starforge-core 0.3.2__tar.gz → 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. {starforge_core-0.3.2 → starforge_core-0.3.4}/.gitignore +2 -0
  2. starforge_core-0.3.4/PKG-INFO +95 -0
  3. starforge_core-0.3.4/README.md +65 -0
  4. {starforge_core-0.3.2 → starforge_core-0.3.4}/pyproject.toml +2 -1
  5. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/__init__.py +1 -1
  6. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/exp.py +2 -3
  7. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/submit.py +4 -7
  8. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/model.py +113 -1
  9. starforge_core-0.3.4/tests/cli/conftest.py +19 -0
  10. starforge_core-0.3.4/tests/cli/test_bench.py +78 -0
  11. starforge_core-0.3.4/tests/cli/test_catalog_handshake.py +130 -0
  12. starforge_core-0.3.4/tests/cli/test_cli.py +381 -0
  13. starforge_core-0.3.4/tests/cli/test_cli_login.py +219 -0
  14. starforge_core-0.3.4/tests/cli/test_cli_ui.py +46 -0
  15. starforge_core-0.3.4/tests/cli/test_config_resolve.py +114 -0
  16. starforge_core-0.3.4/tests/cli/test_maintenance_cli.py +116 -0
  17. starforge_core-0.3.4/tests/cli/test_new_experiment.py +172 -0
  18. starforge_core-0.3.4/tests/cli/test_recipe_lock.py +105 -0
  19. starforge_core-0.3.4/tests/cli/test_repo.py +168 -0
  20. starforge_core-0.3.4/tests/cli/test_spec_builder.py +383 -0
  21. starforge_core-0.3.4/tests/cli/test_submit_pack.py +283 -0
  22. starforge_core-0.3.4/tests/cli/test_sweep.py +153 -0
  23. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_recipes.py +78 -0
  24. {starforge_core-0.3.2 → starforge_core-0.3.4}/uv.lock +1 -1
  25. starforge_core-0.3.2/PKG-INFO +0 -28
  26. {starforge_core-0.3.2 → starforge_core-0.3.4}/gitlab-ci.example.yml +0 -0
  27. {starforge_core-0.3.2 → starforge_core-0.3.4}/pytest.ini +0 -0
  28. {starforge_core-0.3.2 → starforge_core-0.3.4}/scripts/build_and_check.sh +0 -0
  29. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/artifacts.py +0 -0
  30. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/benchmarks/__init__.py +0 -0
  31. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/benchmarks/runners.py +0 -0
  32. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/bootstrap.py +0 -0
  33. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/capsule.py +0 -0
  34. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/capsule_runner.py +0 -0
  35. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/__init__.py +0 -0
  36. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/api_client.py +0 -0
  37. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/auth.py +0 -0
  38. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/catalog.py +0 -0
  39. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/cli.py +0 -0
  40. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/cli_ui.py +0 -0
  41. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/client_device.py +0 -0
  42. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/__init__.py +0 -0
  43. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/admin.py +0 -0
  44. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/bench.py +0 -0
  45. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/common.py +0 -0
  46. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/corpus.py +0 -0
  47. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/dataset.py +0 -0
  48. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/init.py +0 -0
  49. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/jobs.py +0 -0
  50. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/login.py +0 -0
  51. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/plugin.py +0 -0
  52. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/recipe.py +0 -0
  53. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/serve.py +0 -0
  54. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/commands/sweep.py +0 -0
  55. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/config_resolve.py +0 -0
  56. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/data_prep.py +0 -0
  57. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/new_experiment.py +0 -0
  58. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/packing.py +0 -0
  59. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/plugins_lock.py +0 -0
  60. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/recipe_lock.py +0 -0
  61. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/repo.py +0 -0
  62. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/agent-run.py.tmpl +0 -0
  63. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/custom-framework/train.sh +0 -0
  64. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/experiment-template/.gitkeep +0 -0
  65. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/experiment-template/README.md +0 -0
  66. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/experiment-template/config.yaml +0 -0
  67. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/common/README.md +0 -0
  68. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/common/__init__.py +0 -0
  69. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/README.md +0 -0
  70. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/README.md +0 -0
  71. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/distillation_math.yaml +0 -0
  72. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/grpo_lora.yaml +0 -0
  73. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/grpo_math_1B.yaml +0 -0
  74. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/grpo_megatron.yaml +0 -0
  75. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/grpo_noncolocated.yaml +0 -0
  76. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/grpo_sliding_puzzle.yaml +0 -0
  77. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/ppo_math_1B.yaml +0 -0
  78. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/rm.yaml +0 -0
  79. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/base/sft.yaml +0 -0
  80. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/models/README.md +0 -0
  81. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/models/qwen3.5-4b.yaml +0 -0
  82. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/configs/models/qwen3.5-9b.yaml +0 -0
  83. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/scaffold/project/gitignore +0 -0
  84. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/cli/spec_builder.py +0 -0
  85. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/config_resolve.py +0 -0
  86. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/contract/__init__.py +0 -0
  87. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/contract/binding.py +0 -0
  88. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/contract/env.py +0 -0
  89. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/contract/errors.py +0 -0
  90. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/contract/names.py +0 -0
  91. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/contract/spec.py +0 -0
  92. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/contract/topology.py +0 -0
  93. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/datasets.py +0 -0
  94. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/distributed/__init__.py +0 -0
  95. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/distributed/coordinator.py +0 -0
  96. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/distributed/protocol.py +0 -0
  97. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/distributed/ray_runtime.py +0 -0
  98. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/fatal_logs.py +0 -0
  99. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/__init__.py +0 -0
  100. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/actions.py +0 -0
  101. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/base.py +0 -0
  102. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/custom.py +0 -0
  103. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/evalkit.py +0 -0
  104. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/nemo_rl.py +0 -0
  105. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/observability.py +0 -0
  106. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/openrlhf.py +0 -0
  107. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/registry.py +0 -0
  108. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/runtime.py +0 -0
  109. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/trl.py +0 -0
  110. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/frameworks/verl.py +0 -0
  111. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/launcher.py +0 -0
  112. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/lifecycle/__init__.py +0 -0
  113. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/lifecycle/benchmark_eval.py +0 -0
  114. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/lifecycle/export_complete.py +0 -0
  115. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/lifecycle/nemo_eval.py +0 -0
  116. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/lifecycle/nemo_export.py +0 -0
  117. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/lifecycle/verl_eval.py +0 -0
  118. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/log_forward.py +0 -0
  119. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/log_normalize.py +0 -0
  120. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/metrics.py +0 -0
  121. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/observability/__init__.py +0 -0
  122. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/observability/env_probe.py +0 -0
  123. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/observability/hardware_monitor.py +0 -0
  124. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/observability/hw_probe.py +0 -0
  125. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/observability/job_nodes.py +0 -0
  126. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/observability/sampling.py +0 -0
  127. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/observability/sink.py +0 -0
  128. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/observability/util.py +0 -0
  129. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/package_bootstrap.py +0 -0
  130. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/pkce.py +0 -0
  131. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/plugins.py +0 -0
  132. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/__init__.py +0 -0
  133. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/custom/custom/recipe.yaml +0 -0
  134. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/custom/custom/template/README.md +0 -0
  135. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/custom/custom/template/train.sh +0 -0
  136. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/evalkit/benchmark/recipe.yaml +0 -0
  137. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/evalkit/benchmark/template/README.md +0 -0
  138. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/distillation/recipe.yaml +0 -0
  139. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/distillation/template/README.md +0 -0
  140. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/distillation/template/config.yaml +0 -0
  141. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/dpo/recipe.yaml +0 -0
  142. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/dpo/template/README.md +0 -0
  143. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/dpo/template/config.yaml +0 -0
  144. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/grpo/recipe.yaml +0 -0
  145. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/grpo/template/README.md +0 -0
  146. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/grpo/template/config.yaml +0 -0
  147. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/grpo-lora/recipe.yaml +0 -0
  148. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/grpo-lora/template/README.md +0 -0
  149. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/grpo-lora/template/config.yaml +0 -0
  150. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/maxrl/recipe.yaml +0 -0
  151. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/maxrl/template/README.md +0 -0
  152. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/maxrl/template/config.yaml +0 -0
  153. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/maxrl/template/run.py +0 -0
  154. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/opsd/recipe.yaml +0 -0
  155. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/opsd/template/README.md +0 -0
  156. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/opsd/template/config.yaml +0 -0
  157. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/opsd/template/run.py +0 -0
  158. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/ppo/recipe.yaml +0 -0
  159. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/ppo/template/README.md +0 -0
  160. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/ppo/template/config.yaml +0 -0
  161. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/rm/recipe.yaml +0 -0
  162. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/rm/template/README.md +0 -0
  163. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/rm/template/config.yaml +0 -0
  164. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/sft/recipe.yaml +0 -0
  165. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/sft/template/README.md +0 -0
  166. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/nemo-rl/sft/template/config.yaml +0 -0
  167. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/dpo/recipe.yaml +0 -0
  168. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/dpo/template/README.md +0 -0
  169. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/dpo/template/config.yaml +0 -0
  170. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/dpo/template/eval.py +0 -0
  171. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/grpo/recipe.yaml +0 -0
  172. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/grpo/template/README.md +0 -0
  173. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/grpo/template/config.yaml +0 -0
  174. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/grpo/template/eval.py +0 -0
  175. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/ppo/recipe.yaml +0 -0
  176. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/ppo/template/README.md +0 -0
  177. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/ppo/template/config.yaml +0 -0
  178. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/ppo/template/eval.py +0 -0
  179. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/reinforce-baseline/recipe.yaml +0 -0
  180. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/reinforce-baseline/template/README.md +0 -0
  181. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/reinforce-baseline/template/config.yaml +0 -0
  182. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/reinforce-baseline/template/eval.py +0 -0
  183. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/rm/recipe.yaml +0 -0
  184. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/rm/template/README.md +0 -0
  185. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/rm/template/config.yaml +0 -0
  186. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/rm/template/eval.py +0 -0
  187. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/sft/recipe.yaml +0 -0
  188. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/sft/template/README.md +0 -0
  189. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/sft/template/config.yaml +0 -0
  190. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/openrlhf/sft/template/eval.py +0 -0
  191. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/dpo/recipe.yaml +0 -0
  192. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/dpo/template/README.md +0 -0
  193. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/dpo/template/config.yaml +0 -0
  194. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/dpo/template/eval.py +0 -0
  195. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/dpo/template/train.py +0 -0
  196. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/grpo/recipe.yaml +0 -0
  197. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/grpo/template/README.md +0 -0
  198. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/grpo/template/config.yaml +0 -0
  199. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/grpo/template/eval.py +0 -0
  200. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/grpo/template/train.py +0 -0
  201. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/kto/recipe.yaml +0 -0
  202. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/kto/template/README.md +0 -0
  203. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/kto/template/config.yaml +0 -0
  204. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/kto/template/eval.py +0 -0
  205. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/kto/template/train.py +0 -0
  206. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/opsd/recipe.yaml +0 -0
  207. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/opsd/template/README.md +0 -0
  208. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/opsd/template/config.yaml +0 -0
  209. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/opsd/template/eval.py +0 -0
  210. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/opsd/template/train.py +0 -0
  211. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rloo/recipe.yaml +0 -0
  212. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rloo/template/README.md +0 -0
  213. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rloo/template/config.yaml +0 -0
  214. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rloo/template/eval.py +0 -0
  215. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rloo/template/train.py +0 -0
  216. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rm/recipe.yaml +0 -0
  217. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rm/template/README.md +0 -0
  218. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rm/template/config.yaml +0 -0
  219. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rm/template/eval.py +0 -0
  220. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/rm/template/train.py +0 -0
  221. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/sft/recipe.yaml +0 -0
  222. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/sft/template/README.md +0 -0
  223. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/sft/template/config.yaml +0 -0
  224. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/sft/template/eval.py +0 -0
  225. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/trl/sft/template/train.py +0 -0
  226. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/verl/grpo/recipe.yaml +0 -0
  227. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/verl/grpo/template/README.md +0 -0
  228. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/verl/grpo/template/config.yaml +0 -0
  229. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/verl/grpo/template/eval.py +0 -0
  230. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/verl/sft/recipe.yaml +0 -0
  231. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/verl/sft/template/README.md +0 -0
  232. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/verl/sft/template/config.yaml +0 -0
  233. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/catalog/verl/sft/template/eval.py +0 -0
  234. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/recipes/duration.py +0 -0
  235. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/report.py +0 -0
  236. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/reporter.py +0 -0
  237. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/sandbox.py +0 -0
  238. {starforge_core-0.3.2 → starforge_core-0.3.4}/starforge/verl_ipc.py +0 -0
  239. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_architecture_boundaries.py +0 -0
  240. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_artifacts.py +0 -0
  241. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_bootstrap.py +0 -0
  242. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_capsule.py +0 -0
  243. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_config_validate.py +0 -0
  244. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_contract_env_golden.py +0 -0
  245. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_contract_spec.py +0 -0
  246. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_contract_topology.py +0 -0
  247. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_custom_adapter.py +0 -0
  248. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_datasets.py +0 -0
  249. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_distributed_coordinator.py +0 -0
  250. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_env_nodes_probe.py +0 -0
  251. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_env_probe.py +0 -0
  252. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_evalkit.py +0 -0
  253. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_fatal_logs.py +0 -0
  254. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_framework_adapters.py +0 -0
  255. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_framework_observability.py +0 -0
  256. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_framework_runtime.py +0 -0
  257. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_hw_probe.py +0 -0
  258. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_image_fingerprint.py +0 -0
  259. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_job_hw_scope.py +0 -0
  260. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_job_nodes.py +0 -0
  261. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_launcher.py +0 -0
  262. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_lifecycle_commands.py +0 -0
  263. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_log_forward.py +0 -0
  264. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_log_normalize.py +0 -0
  265. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_metrics.py +0 -0
  266. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_nemo_rl_adapter.py +0 -0
  267. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_observability_util.py +0 -0
  268. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_openrlhf_adapter.py +0 -0
  269. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_package_data.py +0 -0
  270. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_process_group_plan.py +0 -0
  271. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_report.py +0 -0
  272. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_reporter.py +0 -0
  273. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_runner.py +0 -0
  274. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_runtime_artifacts.py +0 -0
  275. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_sandbox.py +0 -0
  276. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_training_duration.py +0 -0
  277. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_trl_adapter.py +0 -0
  278. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_verl_adapter.py +0 -0
  279. {starforge_core-0.3.2 → starforge_core-0.3.4}/tests/test_verl_ipc.py +0 -0
@@ -14,6 +14,8 @@ venv/
14
14
  # 前端
15
15
  web/node_modules/
16
16
  web/dist/
17
+ mintlify/scripts/node_modules/
18
+ mintlify/scripts/console-shots.local.json
17
19
 
18
20
  # 本地数据库 / 凭据 / 台账
19
21
  .forge/
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.5
2
+ Name: starforge-core
3
+ Version: 0.3.4
4
+ Summary: StarForge client: JobSpec / recipe contract, job-side runner, and sf CLI.
5
+ Project-URL: Homepage, https://github.com/wccdev/starforge
6
+ Project-URL: Documentation, https://github.com/wccdev/starforge
7
+ Project-URL: Repository, https://github.com/wccdev/starforge
8
+ Keywords: grpo,llm,post-training,rlhf,sft,starforge
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: packaging>=24
20
+ Requires-Dist: pyyaml>=6
21
+ Requires-Dist: typer>=0.12
22
+ Provides-Extra: data
23
+ Requires-Dist: datasets>=2.18; extra == 'data'
24
+ Provides-Extra: dev
25
+ Requires-Dist: fastapi>=0.110; extra == 'dev'
26
+ Requires-Dist: httpx>=0.27; extra == 'dev'
27
+ Requires-Dist: pytest>=8; extra == 'dev'
28
+ Requires-Dist: ruff>=0.5; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # starforge-core
32
+
33
+ Client for StarForge. You get the `sf` command and a Python package named `starforge`.
34
+
35
+ Use this to scaffold a lab, lock a post-training method, and submit jobs to a StarForge console. The control plane is a separate install, `starforge-server`. Training images do not need this whole package; the server injects a runner that carries the kernel only.
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install starforge-core
41
+ # or
42
+ uv tool install starforge-core
43
+
44
+ sf --help
45
+ ```
46
+
47
+ Python 3.10 or newer. Import name is `starforge`, not `starforge_core`.
48
+
49
+ `pip install starforge-core[data]` adds HuggingFace `datasets` for local prepare scripts.
50
+
51
+ ## Start a lab
52
+
53
+ Do not clone the platform repo. This package writes a project for you:
54
+
55
+ ```bash
56
+ sf init my-lab --yes
57
+ cd my-lab
58
+ sf login --server https://<your-console>
59
+ sf new my-grpo --method nemo-rl/grpo
60
+ sf validate my-grpo
61
+ sf submit my-grpo --profile h200:8
62
+ sf job logs
63
+ ```
64
+
65
+ `sf methods` lists the catalog on the server you logged into. Methods look like `nemo-rl/grpo`, `verl/sft`, `trl/kto`, `openrlhf/ppo`, `evalkit/benchmark`, `custom/custom`.
66
+
67
+ Examples live in `starforge-tutorial`. Day-to-day work should be your own `sf init` tree.
68
+
69
+ ## Python
70
+
71
+ Catalog methods already forward framework logs. Custom `train.sh` jobs that want console curves call:
72
+
73
+ ```python
74
+ from starforge.report import init, log, finish
75
+
76
+ init()
77
+ log({"loss": 0.12}, step=1)
78
+ finish()
79
+ ```
80
+
81
+ `starforge.spec_to_env` is the JobSpec to environment mapping used on both the client and the job. `starforge.get_recipe` / `starforge.recipe_names` read the bundled catalog.
82
+
83
+ ## What this package contains
84
+
85
+ - `sf` CLI: init, login, experiments, submit, sweep, jobs, datasets, plugins, bench, serve
86
+ - JobSpec types and `spec_to_env`
87
+ - Recipe catalog and framework adapters (NeMo-RL, verl, TRL, OpenRLHF, evalkit, custom)
88
+ - Job-side reporter (`starforge.report`)
89
+
90
+ Cluster credentials stay on the server. Your laptop keeps an access token under `~/.forge/`.
91
+
92
+ ## Docs and source
93
+
94
+ - Platform repo: https://github.com/wccdev/starforge
95
+ - User docs: `mintlify/` in that repo (English and 简体中文)
@@ -0,0 +1,65 @@
1
+ # starforge-core
2
+
3
+ Client for StarForge. You get the `sf` command and a Python package named `starforge`.
4
+
5
+ Use this to scaffold a lab, lock a post-training method, and submit jobs to a StarForge console. The control plane is a separate install, `starforge-server`. Training images do not need this whole package; the server injects a runner that carries the kernel only.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install starforge-core
11
+ # or
12
+ uv tool install starforge-core
13
+
14
+ sf --help
15
+ ```
16
+
17
+ Python 3.10 or newer. Import name is `starforge`, not `starforge_core`.
18
+
19
+ `pip install starforge-core[data]` adds HuggingFace `datasets` for local prepare scripts.
20
+
21
+ ## Start a lab
22
+
23
+ Do not clone the platform repo. This package writes a project for you:
24
+
25
+ ```bash
26
+ sf init my-lab --yes
27
+ cd my-lab
28
+ sf login --server https://<your-console>
29
+ sf new my-grpo --method nemo-rl/grpo
30
+ sf validate my-grpo
31
+ sf submit my-grpo --profile h200:8
32
+ sf job logs
33
+ ```
34
+
35
+ `sf methods` lists the catalog on the server you logged into. Methods look like `nemo-rl/grpo`, `verl/sft`, `trl/kto`, `openrlhf/ppo`, `evalkit/benchmark`, `custom/custom`.
36
+
37
+ Examples live in `starforge-tutorial`. Day-to-day work should be your own `sf init` tree.
38
+
39
+ ## Python
40
+
41
+ Catalog methods already forward framework logs. Custom `train.sh` jobs that want console curves call:
42
+
43
+ ```python
44
+ from starforge.report import init, log, finish
45
+
46
+ init()
47
+ log({"loss": 0.12}, step=1)
48
+ finish()
49
+ ```
50
+
51
+ `starforge.spec_to_env` is the JobSpec to environment mapping used on both the client and the job. `starforge.get_recipe` / `starforge.recipe_names` read the bundled catalog.
52
+
53
+ ## What this package contains
54
+
55
+ - `sf` CLI: init, login, experiments, submit, sweep, jobs, datasets, plugins, bench, serve
56
+ - JobSpec types and `spec_to_env`
57
+ - Recipe catalog and framework adapters (NeMo-RL, verl, TRL, OpenRLHF, evalkit, custom)
58
+ - Job-side reporter (`starforge.report`)
59
+
60
+ Cluster credentials stay on the server. Your laptop keeps an access token under `~/.forge/`.
61
+
62
+ ## Docs and source
63
+
64
+ - Platform repo: https://github.com/wccdev/starforge
65
+ - User docs: `mintlify/` in that repo (English and 简体中文)
@@ -1,8 +1,9 @@
1
1
  # Researcher client: contract kernel + sf CLI. Control plane is starforge-server.
2
2
  [project]
3
3
  name = "starforge-core"
4
- version = "0.3.2"
4
+ version = "0.3.4"
5
5
  description = "StarForge client: JobSpec / recipe contract, job-side runner, and sf CLI."
6
+ readme = "README.md"
6
7
  requires-python = ">=3.10"
7
8
  keywords = ["llm", "post-training", "rlhf", "grpo", "sft", "starforge"]
8
9
  classifiers = [
@@ -5,7 +5,7 @@
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
- __version__ = "0.3.2"
8
+ __version__ = "0.3.4"
9
9
 
10
10
  from .contract import (
11
11
  API_VERSION,
@@ -114,11 +114,10 @@ def _validate_exp_contents(exp_dir, recipe, *, repo_root=None) -> tuple[list[str
114
114
 
115
115
  def _validate_exp(exp_path: str, recipe_override: str = "") -> tuple[list[str], list[str]]:
116
116
  """锁必须是当前 bundle,再跑 recipe 所属框架的 validator。"""
117
- from starforge.contract import SpecError
118
- from starforge.recipes import get_recipe
119
-
120
117
  from starforge.cli.recipe_lock import validate_recipe_lock
121
118
  from starforge.cli.spec_builder import infer_recipe
119
+ from starforge.contract import SpecError
120
+ from starforge.recipes import get_recipe
122
121
 
123
122
  exp_dir = common.ROOT / exp_path
124
123
  recipe_name = recipe_override.strip() or infer_recipe(exp_dir)
@@ -174,9 +174,8 @@ def _materialize_profile_or_exit(exprs: list[str]) -> tuple[str, list[str], list
174
174
  series 与默认形状查服务端注册表——用户只说「哪种卡、几张」,
175
175
  拓扑细节由注册表补齐,两边不可能写出互相矛盾的资源声明。
176
176
  """
177
- from starforge.contract import SpecError
178
-
179
177
  from starforge.cli import spec_builder
178
+ from starforge.contract import SpecError
180
179
 
181
180
  try:
182
181
  return spec_builder.materialize_pools(exprs, common.profile_registry())
@@ -229,9 +228,8 @@ def _build_spec_or_exit(exp_path: str, *, method, project, sets, pools, roles,
229
228
  framework_version=None, image=None, provenance=None,
230
229
  validate: bool):
231
230
  """构建强 JobSpec;缺少显式 recipe 时立即退出。"""
232
- from starforge.contract import SpecError
233
-
234
231
  from starforge.cli import spec_builder
232
+ from starforge.contract import SpecError
235
233
 
236
234
  recipe = (method or "").strip() or spec_builder.infer_recipe(common.ROOT / exp_path)
237
235
  if not recipe:
@@ -342,11 +340,10 @@ def _echo_upload_summary(res: dict) -> None:
342
340
  def _submit_post(action: str, exp_path: str, profile: Optional[str], flags: list[str],
343
341
  dry_run: bool, allow_dirty: bool) -> int:
344
342
  """构建并预编译训练后强契约,再通过统一 launcher 提交。"""
345
- from starforge.frameworks import CompileRequest, compile_launch_plan
346
- from starforge.recipes import get_recipe
347
-
348
343
  from starforge.cli import spec_builder
349
344
  from starforge.cli.recipe_lock import validate_recipe_lock
345
+ from starforge.frameworks import CompileRequest, compile_launch_plan
346
+ from starforge.recipes import get_recipe
350
347
 
351
348
  recipe_name = spec_builder.infer_recipe(common.ROOT / exp_path)
352
349
  if not recipe_name:
@@ -542,6 +542,77 @@ class Entrypoint:
542
542
  return cls(kind=kind, value=value, experiment_override=override)
543
543
 
544
544
 
545
+ #: 指标方向:higher = 越高越好,lower = 越低越好,neutral = 两头都不好 / 无优劣。
546
+ METRIC_DIRECTIONS = ("higher", "lower", "neutral")
547
+ #: 展示形态:ratio = 0–1 的占比(展示为百分比,差值展示为百分点),number = 裸数值。
548
+ METRIC_DISPLAYS = ("ratio", "number")
549
+
550
+
551
+ @dataclass(frozen=True)
552
+ class MetricSemantics:
553
+ """一个 canonical 指标「怎么读」:方向与展示形态。
554
+
555
+ 这两件事此前只存在于 console 的键名正则里,而且是三份互不相同的正则 ——
556
+ `train/clip_frac` 在图表页按百分比画、在对比页按裸小数显示,同一个数字两种读法。
557
+ 根因是契约声明了指标的**名字**却没声明它的**含义**,前端只能按名字猜。
558
+ """
559
+
560
+ direction: str = "higher"
561
+ display: str = "number"
562
+
563
+ def to_dict(self) -> dict[str, str]:
564
+ return {"direction": self.direction, "display": self.display}
565
+
566
+
567
+ #: canonical 词表的内置语义。与 MetricContract docstring 里的词表逐条对应 ——
568
+ #: 新增 canonical 名时在这里补一行,recipe 无须逐个声明。
569
+ #:
570
+ #: 策略熵是 neutral 而不是 lower:RL 里熵归零就是策略塌缩、停止探索,判成 lower
571
+ #: 会让对比页把塌缩掉的那次训练标成最优;判成 higher 又会奖励纯随机策略。两头
572
+ #: 都不好的量就该说自己两头都不好,而不是被塞进一个二选一里。
573
+ CANONICAL_METRIC_SEMANTICS: dict[str, MetricSemantics] = {
574
+ "train/reward": MetricSemantics("higher", "number"),
575
+ "train/score": MetricSemantics("higher", "number"),
576
+ "train/loss": MetricSemantics("lower", "number"),
577
+ "train/kl_penalty": MetricSemantics("lower", "number"),
578
+ "train/entropy": MetricSemantics("neutral", "number"),
579
+ "train/grad_norm": MetricSemantics("lower", "number"),
580
+ "train/lr": MetricSemantics("neutral", "number"),
581
+ "train/response_length": MetricSemantics("neutral", "number"),
582
+ "train/clip_frac": MetricSemantics("lower", "ratio"),
583
+ "train/truncated_ratio": MetricSemantics("lower", "ratio"),
584
+ "train/reward_zero_std_frac": MetricSemantics("lower", "ratio"),
585
+ "train/value_loss": MetricSemantics("lower", "number"),
586
+ "train/value_explained_var": MetricSemantics("higher", "number"),
587
+ "train/accuracy": MetricSemantics("higher", "ratio"),
588
+ "train/token_accuracy": MetricSemantics("higher", "ratio"),
589
+ "train/margin": MetricSemantics("higher", "number"),
590
+ # 偏好对两侧的隐式奖励:绝对值无意义(看的是间隔 train/margin),
591
+ # 单独判优劣会把「两边一起涨」误读成进步。
592
+ "train/reward_chosen": MetricSemantics("neutral", "number"),
593
+ "train/reward_rejected": MetricSemantics("neutral", "number"),
594
+ "validation/accuracy": MetricSemantics("higher", "ratio"),
595
+ "validation/token_accuracy": MetricSemantics("higher", "ratio"),
596
+ "validation/loss": MetricSemantics("lower", "number"),
597
+ "validation/reward": MetricSemantics("higher", "number"),
598
+ "validation/margin": MetricSemantics("higher", "number"),
599
+ "validation/kl_penalty": MetricSemantics("lower", "number"),
600
+ "train/return": MetricSemantics("higher", "number"),
601
+ "train/token_mult_prob_error": MetricSemantics("lower", "number"),
602
+ # 优势/价值/长度这类量没有「越大越好」:优势均值应当在 0 附近(GAE 已中心化),
603
+ # critic 的 value 是对回报的估计而非目标,生成长度长短各有代价。
604
+ "train/advantages/mean": MetricSemantics("neutral", "number"),
605
+ "train/value": MetricSemantics("neutral", "number"),
606
+ "train/opsd_hint_len_mean": MetricSemantics("neutral", "number"),
607
+ "train/opsd_response_len_mean": MetricSemantics("neutral", "number"),
608
+ "train/opsd_kl_clip_frac": MetricSemantics("lower", "ratio"),
609
+ # 组内奖励标准差:归零是 GRPO 的失效信号(组内全同分,梯度为零),
610
+ # 过大则说明采样噪声压过了信号。同 entropy,两头都不好。
611
+ "train/reward_std": MetricSemantics("neutral", "number"),
612
+ "bench/score": MetricSemantics("higher", "number"),
613
+ }
614
+
615
+
545
616
  @dataclass(frozen=True)
546
617
  class MetricContract:
547
618
  """指标契约:把各框架的原生键名收敛到一套跨框架可比的 canonical 名字。
@@ -585,6 +656,23 @@ class MetricContract:
585
656
 
586
657
  primary: tuple[str, ...]
587
658
  aliases: dict[str, tuple[str, ...]]
659
+ #: 对 CANONICAL_METRIC_SEMANTICS 的按 recipe 覆写。绝大多数 recipe 用不上 ——
660
+ #: canonical 名字一样,含义就一样,那正是收敛到 canonical 词表的意义。
661
+ semantics: dict[str, MetricSemantics] = field(default_factory=dict)
662
+
663
+ def semantics_for(self, key: str) -> MetricSemantics:
664
+ """该指标怎么读。未知键(含 `raw/` 前缀的未声明键)落到默认值。"""
665
+ if (own := self.semantics.get(key)) is not None:
666
+ return own
667
+ return CANONICAL_METRIC_SEMANTICS.get(key, MetricSemantics())
668
+
669
+ def semantics_map(self) -> dict[str, dict[str, str]]:
670
+ """本契约全部 canonical 名的语义表 —— 服务端据此下发给 console。
671
+
672
+ canonical 名 = primary ∪ aliases 的键,与 starforge.metrics 的归一化口径同源。
673
+ """
674
+ names = list(self.primary) + [k for k in self.aliases if k not in self.primary]
675
+ return {name: self.semantics_for(name).to_dict() for name in names}
588
676
 
589
677
  @classmethod
590
678
  def from_dict(cls, data: Mapping[str, Any] | None, *, recipe: str) -> MetricContract:
@@ -601,7 +689,26 @@ class MetricContract:
601
689
  if isinstance(values, (str, bytes)) or not isinstance(values, Sequence):
602
690
  raise SpecError("应为字符串数组", field=f"metrics.aliases.{canonical}")
603
691
  aliases[str(canonical)] = tuple(str(v) for v in values)
604
- return cls(primary=tuple(str(v) for v in primary_raw), aliases=aliases)
692
+ semantics: dict[str, MetricSemantics] = {}
693
+ for canonical, raw in (data.get("semantics") or {}).items():
694
+ if not isinstance(raw, Mapping):
695
+ raise SpecError("应为对象(direction / display)", field=f"metrics.semantics.{canonical}")
696
+ direction = str(raw.get("direction", "higher"))
697
+ display = str(raw.get("display", "number"))
698
+ if direction not in METRIC_DIRECTIONS:
699
+ raise SpecError(
700
+ f"direction 必须是 {'/'.join(METRIC_DIRECTIONS)} 之一,得到 {direction!r}",
701
+ field=f"metrics.semantics.{canonical}.direction",
702
+ )
703
+ if display not in METRIC_DISPLAYS:
704
+ raise SpecError(
705
+ f"display 必须是 {'/'.join(METRIC_DISPLAYS)} 之一,得到 {display!r}",
706
+ field=f"metrics.semantics.{canonical}.display",
707
+ )
708
+ semantics[str(canonical)] = MetricSemantics(direction, display)
709
+ return cls(
710
+ primary=tuple(str(v) for v in primary_raw), aliases=aliases, semantics=semantics
711
+ )
605
712
 
606
713
 
607
714
  #: 验证样本形态:conversation(RL 对话+奖励)/ preference(DPO 偏好对)/
@@ -924,6 +1031,11 @@ class Recipe:
924
1031
  def metric_aliases(self) -> dict[str, tuple[str, ...]]:
925
1032
  return dict(self.metrics.aliases)
926
1033
 
1034
+ @property
1035
+ def metric_semantics(self) -> dict[str, dict[str, str]]:
1036
+ """本方法全部 canonical 指标的「怎么读」。服务端随 runtime_contract 下发。"""
1037
+ return self.metrics.semantics_map()
1038
+
927
1039
  @property
928
1040
  def max_steps_path(self) -> str | None:
929
1041
  """训练步数上限超参在配置树里的落点(如 grpo.max_num_steps / max_steps)。
@@ -0,0 +1,19 @@
1
+ """CLI 单测默认在临时 StarForge 项目内运行(不再依赖 dogfood 仓里的 experiments/)。"""
2
+ from __future__ import annotations
3
+
4
+ import pytest
5
+
6
+ from starforge.cli.commands import common
7
+ from starforge.cli.repo import init_repo
8
+
9
+
10
+ @pytest.fixture(autouse=True)
11
+ def cli_project(tmp_path, monkeypatch):
12
+ root = init_repo(tmp_path / "sf-project", name="cli-tests", git=False)
13
+ (root / "experiments" / "demo").mkdir(exist_ok=True)
14
+ # ROOT 由 __getattr__ 惰性提供;setattr 会先 getattr 触发 project_root()。
15
+ # 必须写进模块字典,后续测试的 monkeypatch.setattr 才能覆盖。
16
+ monkeypatch.setitem(common.__dict__, "ROOT", root)
17
+ monkeypatch.setenv("SF_REPO_ROOT", str(root))
18
+ monkeypatch.chdir(root)
19
+ return root
@@ -0,0 +1,78 @@
1
+ """sf bench:模型引用解析与评测提交参数翻译。"""
2
+ from __future__ import annotations
3
+
4
+ import io
5
+ import json
6
+
7
+ from typer.testing import CliRunner
8
+
9
+ from starforge.cli import cli
10
+ from starforge.cli.commands import bench as bench_cmd
11
+
12
+ runner = CliRunner()
13
+
14
+
15
+ def test_run_ref_resolves_hf_export(monkeypatch):
16
+ class _Resp(io.BytesIO):
17
+ def __enter__(self):
18
+ return self
19
+
20
+ def __exit__(self, *a):
21
+ return False
22
+
23
+ def fake_bearer(srv, method, path):
24
+ assert path == "/api/jobs/run-42/artifacts"
25
+ return _Resp(json.dumps({"artifacts": [
26
+ {"kind": "checkpoint", "path": "/out/step_100"},
27
+ {"kind": "hf_export", "path": "/out/hf_export"},
28
+ ]}).encode())
29
+
30
+ monkeypatch.setattr(bench_cmd.api_client, "current_server", lambda *a: "http://srv")
31
+ monkeypatch.setattr(bench_cmd.api_client, "_bearer_request", fake_bearer)
32
+ assert bench_cmd._resolve_model_ref("run:run-42") == "/out/hf_export"
33
+ # 非 run: 引用原样返回
34
+ assert bench_cmd._resolve_model_ref("Qwen/Qwen3-0.6B") == "Qwen/Qwen3-0.6B"
35
+
36
+
37
+ def test_bench_submits_evalkit_spec(monkeypatch, tmp_path):
38
+ (tmp_path / "experiments" / "bench-demo").mkdir(parents=True)
39
+ calls = {}
40
+
41
+ monkeypatch.setattr(bench_cmd.common, "ROOT", tmp_path)
42
+ monkeypatch.setattr(bench_cmd.common, "project_name", lambda: "test")
43
+ monkeypatch.setattr(bench_cmd, "gate", lambda: None)
44
+ monkeypatch.setattr(bench_cmd.common, "resolve_profile", lambda *a, **k: "h200")
45
+ monkeypatch.setattr(
46
+ bench_cmd, "_materialize_profile_or_exit",
47
+ lambda exprs: ("h200", ["eval:h200:1:1"], []),
48
+ )
49
+ monkeypatch.setattr(
50
+ bench_cmd.packing, "git_provenance",
51
+ lambda *a, **k: {"git_commit": "cafe", "git_dirty": False, "config_sha": "x"},
52
+ )
53
+
54
+ def fake_build_spec(exp_path, **kw):
55
+ calls["method"] = kw["method"]
56
+ calls["sets"] = kw["sets"]
57
+ calls["model"] = kw["model"]
58
+ calls["validate"] = kw["validate"]
59
+ return {"spec": True}
60
+
61
+ monkeypatch.setattr(bench_cmd, "_build_spec_or_exit", fake_build_spec)
62
+ monkeypatch.setattr(
63
+ bench_cmd.api_client, "submit_via_server",
64
+ lambda *a, **k: {"queued": False, "job_id": "ray-1", "run_id": "r1"},
65
+ )
66
+
67
+ res = runner.invoke(cli.app, [
68
+ "bench", "experiments/bench-demo",
69
+ "--model", "Qwen/Qwen3-0.6B",
70
+ "--suites", "gsm8k,mmlu",
71
+ "--runner", "evalscope",
72
+ "--limit", "20",
73
+ ])
74
+ assert res.exit_code == 0, res.stdout
75
+ assert calls["method"] == "evalkit/benchmark"
76
+ assert calls["model"] == "Qwen/Qwen3-0.6B"
77
+ assert calls["sets"] == ["runner=evalscope", "suites=gsm8k,mmlu", "limit=20"]
78
+ assert calls["validate"] is False
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+
6
+ import pytest
7
+
8
+ from starforge import __version__ as CORE_VERSION
9
+ from starforge.cli.catalog import CatalogCompatibilityError, verify_catalog_compatibility
10
+ from starforge.cli.spec_builder import build_spec
11
+ from starforge.recipes import get_recipe
12
+
13
+
14
+ def _payload(spec, **recipe_overrides):
15
+ recipe = get_recipe(spec.recipe_name)
16
+ item = {
17
+ "name": recipe.id,
18
+ "version": recipe.version,
19
+ "digest": recipe.digest,
20
+ "framework": recipe.framework,
21
+ "adapter": recipe.framework,
22
+ "framework_version": recipe.runtime.default_version,
23
+ "supported_framework_versions": list(recipe.runtime.supported_versions),
24
+ "runtime": recipe.runtime.to_dict(),
25
+ }
26
+ item.update(recipe_overrides)
27
+ canonical = [{"name": item["name"], "version": item["version"], "digest": item["digest"]}]
28
+ digest = hashlib.sha256(
29
+ json.dumps(canonical, sort_keys=True, separators=(",", ":")).encode()
30
+ ).hexdigest()
31
+ return {
32
+ "apiVersion": "forge/recipe-catalog/v2",
33
+ "contract": {"versions": ["forge/v2"]},
34
+ "core": {"version": "0.1.0", "requirement": ">=0.1,<1"},
35
+ "catalog_digest": f"sha256:{digest}",
36
+ "recipes": [item],
37
+ }
38
+
39
+
40
+ def _spec():
41
+ return build_spec("experiments/demo", recipe="nemo-rl/grpo", pools=["all:h100:1:1"])
42
+
43
+
44
+ def test_exact_catalog_handshake_accepts_matching_recipe():
45
+ verify_catalog_compatibility(_spec(), _payload(_spec()))
46
+
47
+
48
+ @pytest.mark.parametrize(
49
+ "field,value",
50
+ [("version", "0.0.0"), ("digest", "sha256:wrong"), ("framework", "verl"), ("adapter", "verl")],
51
+ )
52
+ def test_catalog_handshake_rejects_recipe_drift(field, value):
53
+ spec = _spec()
54
+ with pytest.raises(CatalogCompatibilityError):
55
+ verify_catalog_compatibility(spec, _payload(spec, **{field: value}))
56
+
57
+
58
+ def test_catalog_handshake_rejects_core_drift():
59
+ spec = _spec()
60
+ payload = _payload(spec)
61
+ payload["core"] = {"version": "9.0.0", "requirement": ">=9,<10"}
62
+ with pytest.raises(CatalogCompatibilityError, match="core"):
63
+ verify_catalog_compatibility(spec, payload)
64
+
65
+
66
+ def test_catalog_handshake_accepts_compatible_core_range():
67
+ spec = _spec()
68
+ payload = _payload(spec)
69
+ payload["core"] = {"version": "0.2.0", "requirement": ">=0.1,<1"}
70
+ verify_catalog_compatibility(spec, payload)
71
+
72
+
73
+ def test_catalog_handshake_still_accepts_legacy_v1_exact_match():
74
+ spec = _spec()
75
+ payload = _payload(spec)
76
+ payload["apiVersion"] = "forge/recipe-catalog/v1"
77
+ payload["core"] = {"version": CORE_VERSION, "requirement": f"=={CORE_VERSION}"}
78
+ verify_catalog_compatibility(spec, payload)
79
+
80
+
81
+ def test_catalog_handshake_rejects_tampered_catalog_digest():
82
+ spec = _spec()
83
+ payload = _payload(spec)
84
+ payload["catalog_digest"] = "sha256:tampered"
85
+ with pytest.raises(CatalogCompatibilityError, match="catalog digest"):
86
+ verify_catalog_compatibility(spec, payload)
87
+
88
+
89
+ def test_catalog_handshake_rejects_framework_version_or_runtime_id_drift():
90
+ spec = _spec()
91
+ payload = _payload(spec, supported_framework_versions=["9.9.9"])
92
+ with pytest.raises(CatalogCompatibilityError, match="未发布"):
93
+ verify_catalog_compatibility(spec, payload)
94
+
95
+ payload = _payload(spec)
96
+ payload["recipes"][0]["runtime"]["versions"]["0.7.0"]["runtime_id"] = "wrong-runtime"
97
+ with pytest.raises(CatalogCompatibilityError, match="runtime_id"):
98
+ verify_catalog_compatibility(spec, payload)
99
+
100
+
101
+ def test_submit_does_not_package_when_handshake_fails(tmp_path, monkeypatch):
102
+ from starforge.cli import api_client
103
+
104
+ spec = _spec()
105
+ packed = []
106
+
107
+ monkeypatch.setattr(api_client, "current_server", lambda _server=None: "https://lab.example")
108
+ monkeypatch.setattr(
109
+ api_client,
110
+ "verify_server_compatibility",
111
+ lambda *_args, **_kwargs: (_ for _ in ()).throw(CatalogCompatibilityError("drift")),
112
+ )
113
+ monkeypatch.setattr(
114
+ api_client,
115
+ "pack_working_dir",
116
+ lambda *_args, **_kwargs: packed.append(True),
117
+ )
118
+
119
+ with pytest.raises(CatalogCompatibilityError, match="drift"):
120
+ api_client.submit_via_server(
121
+ "experiments/demo", "h100", tmp_path, spec=spec
122
+ )
123
+ assert packed == []
124
+
125
+
126
+ def test_submit_requires_explicit_profile(tmp_path):
127
+ from starforge.cli import api_client
128
+
129
+ with pytest.raises(ValueError, match="profile"):
130
+ api_client.submit_via_server("experiments/demo", "", tmp_path, spec=_spec())