soup-cli 0.40.0__tar.gz → 0.40.2__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.
- {soup_cli-0.40.0 → soup_cli-0.40.2}/CONTRIBUTING.md +1 -1
- {soup_cli-0.40.0 → soup_cli-0.40.2}/PKG-INFO +16 -9
- {soup_cli-0.40.0 → soup_cli-0.40.2}/README.md +14 -7
- {soup_cli-0.40.0 → soup_cli-0.40.2}/SECURITY.md +4 -1
- {soup_cli-0.40.0 → soup_cli-0.40.2}/pyproject.toml +2 -2
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/__init__.py +1 -1
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/autopilot/analyzer.py +56 -3
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/cli.py +27 -10
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/bench.py +23 -10
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/data.py +78 -19
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/deploy.py +25 -4
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/doctor.py +138 -10
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/eval.py +24 -11
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/history.py +23 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/infer.py +65 -5
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/init.py +15 -3
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/migrate.py +29 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/quickstart.py +95 -9
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/recipes.py +22 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/runs.py +39 -1
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/train.py +7 -2
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/config/schema.py +32 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/loader.py +6 -1
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/monitoring/display.py +44 -1
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/monitoring/hf_push.py +42 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/preference.py +70 -11
- soup_cli-0.40.2/soup_cli/utils/encoding.py +43 -0
- soup_cli-0.40.2/soup_cli/utils/hf_space.py +94 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/log_level.py +13 -0
- soup_cli-0.40.2/soup_cli/utils/preference_combine.py +184 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_data_sample.py +4 -2
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_pissa_init.py +25 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_preference_multi.py +5 -7
- soup_cli-0.40.2/tests/test_preference_multi_runtime.py +249 -0
- soup_cli-0.40.2/tests/test_v0401_part_c.py +182 -0
- soup_cli-0.40.2/tests/test_v0401_part_d.py +125 -0
- soup_cli-0.40.2/tests/test_v0401_part_e.py +84 -0
- soup_cli-0.40.2/tests/test_v0402_part_a.py +370 -0
- soup_cli-0.40.2/tests/test_v0402_part_b.py +251 -0
- soup_cli-0.40.2/tests/test_windows_encoding.py +121 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.dockerignore +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.github/FUNDING.yml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.github/pull_request_template.md +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.github/workflows/ci.yml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.github/workflows/docker.yml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.github/workflows/publish.yml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.github/workflows/recipe-validation.yml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/.gitignore +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/CODEOWNERS +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/CODE_OF_CONDUCT.md +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/Dockerfile +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/LICENSE +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/NOTICE +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/docker-compose.yml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/README.md +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/configs/dpo_chat.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/configs/dpo_example.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/configs/grpo_reasoning.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/configs/rlhf_step1_sft.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/configs/rlhf_step2_reward.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/configs/rlhf_step3_ppo.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/configs/sft_basic.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/configs/vision_llama.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/data/alpaca_tiny.jsonl +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/data/chat_preferences.jsonl +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/data/dpo_sample.jsonl +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/examples/data/reasoning_math.jsonl +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup.png +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/__main__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/autopilot/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/autopilot/decisions.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/autopilot/generate_config.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/cans/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/cans/pack.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/cans/publish.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/cans/run.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/cans/schema.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/cans/unpack.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/cans/verify.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/adapters.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/autopilot.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/can.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/chat.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/cost.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/diff.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/export.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/generate.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/merge.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/profile.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/push.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/registry.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/serve.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/sweep.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/tui.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/ui.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/commands/why.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/config/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/config/loader.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/augment.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/chat_templates.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/collators.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/formats.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/loss_mask.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/providers/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/providers/_utils.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/providers/anthropic.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/providers/ollama.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/providers/vllm.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/sft_format.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/templates/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/templates/code.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/templates/conversation.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/templates/preference.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/templates/qa.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/templates/reasoning.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/templates/tool_calling.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/templates/verifiable.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/traces/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/traces/pair_builder.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/traces/parsers.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/data/validator.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/eval/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/eval/checkpoint_intelligence.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/eval/custom.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/eval/forgetting.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/eval/gate.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/eval/human.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/eval/judge.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/eval/leaderboard.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/eval/quant_check.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/experiment/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/experiment/tracker.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/migrate/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/migrate/axolotl.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/migrate/common.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/migrate/llamafactory.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/migrate/unsloth.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/monitoring/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/monitoring/callback.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/recipes/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/recipes/catalog.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/registry/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/registry/attach.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/registry/diff.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/registry/hashing.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/registry/store.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/audio.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/bco.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/chat.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/code.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/embedding.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/ipo.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/kto.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/longcontext.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/manifest.json +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/medical.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/moe.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/orpo.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/pretrain.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/reasoning.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/rlhf.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/simpo.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/tool-calling.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/templates/vision.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/bco.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/dpo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/embedding.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/grpo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/ipo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/kto.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/mlx_dpo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/mlx_grpo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/mlx_routing.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/mlx_sft.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/orpo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/ppo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/pretrain.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/reward_model.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/rewards.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/sft.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/trainer/simpo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/tui_app.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/ui/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/ui/app.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/ui/static/app.js +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/ui/static/index.html +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/ui/static/logo.png +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/ui/static/logo.svg +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/ui/static/style.css +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/activation_offload.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/auto_quant.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/batch_probe.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/constants.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/convergence.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/crash.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/cross_doc_attn.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/curriculum.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/cut_ce.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/deepspeed.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/dpo_variants.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/errors.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/flash_attn.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/fp8.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/freeze.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/fsdp.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/galore.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/gpu.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/grad_accum.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/gradient_ckpt.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/hf.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/jinja_analyzer.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/kernel_picker.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/launcher.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/liger.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/long_context.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/lr_finder.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/metrics.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/mii.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/mixed_precision.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/mlx.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/moe.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/multipack.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/multipack_sampler.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/neat_packing.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/ollama.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/paths.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/peft_builder.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/peft_patches.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/pipeline.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/profiler.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/profiling.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/qat.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/quality.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/quant_menu.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/registry.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/relora.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/replay.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/ring_attention.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/run_cost.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/sglang.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/spec_pairing.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/spike_recovery.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/structured_output.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/topology.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/tracing.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/trust_remote.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/unsloth.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/v028_features.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/vllm.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/warmup.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_cli/utils/why.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/soup_logo_svg.svg +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/templates/chat.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/templates/code.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/templates/medical.yaml +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/__init__.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/conftest.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_adapters.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_advanced_peft.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_assistant_mask.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_audio.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_auto_tuning.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_autopilot.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_awq_gptq_export.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_batch_probe.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_bco.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_bench.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_bugfixes.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_callback.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_cans.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_chat.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_chat_template.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_cli.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_cli_subprocess.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_config.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_cost.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_crash_reporter.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_curriculum.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_data.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_data_augment.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_data_split.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_data_tools.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_dataset_hub.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_dataset_registry.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_deepspeed.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_deploy_ollama.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_diff.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_display.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_doctor.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_dpo_example.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_dpo_variants.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_embedding.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_errors.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_eval.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_eval_gate.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_eval_platform.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_export.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_formats.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_fp8_recipe.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_freeze_training.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_generate.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_gpu.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_grpo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_hf_integration.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_infer.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_inference_advanced.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_init.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_ipo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_jinja_analyzer.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_kto.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_loader.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_log_level.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_loss_watchdog.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_merge.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_migrate.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_mlx_backend.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_moe.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_multi_adapter.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_multi_gpu.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_multipack_config.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_multipack_invariants.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_multipack_sampler.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_neat_packing.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_neftune_rslora.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_onnx_tensorrt_export.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_orpo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_packing.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_part_a_wave1.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_part_a_wave2.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_part_b.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_part_c.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_part_d.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_part_e.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_part_f_hardening.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_peft_methods.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_peft_patches.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_performance.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_ppo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_preference_dispatcher.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_pretrain.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_profile.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_profiling.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_progress.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_push.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_qat.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_quality_filter.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_quant_check.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_quant_menu.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_quickstart.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_rank_pattern.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_recipes.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_recipes_v031.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_registry.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_relora.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_replay.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_resume.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_rlvr.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_run_cost.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_runs.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_serve.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_server_generate.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_sglang_serve.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_simpo.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_smoke_train.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_speculative_decoding.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_sweep.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_synth_data_pro.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_templates_yaml.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_tensorboard.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_tool_calling.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_trace_to_pref.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_tracker.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_trainer_coverage_v035.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_trainer_init.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_training_intelligence.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_training_speed.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_trust_remote_code.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_tui.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_ui.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_ui_chat.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_ui_config_builder.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_ui_live_monitor.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_ui_metrics.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_unsloth.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_validator.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_vision.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_vllm_serve.py +0 -0
- {soup_cli-0.40.0 → soup_cli-0.40.2}/tests/test_why.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 (
|
|
114
|
+
tests/ - Test suite (143 files, 4756 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.40.
|
|
3
|
+
Version: 0.40.2
|
|
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
|
|
@@ -27,7 +27,7 @@ Requires-Dist: pydantic>=2.0.0
|
|
|
27
27
|
Requires-Dist: pyyaml>=6.0
|
|
28
28
|
Requires-Dist: rich>=13.0.0
|
|
29
29
|
Requires-Dist: torch>=2.0.0
|
|
30
|
-
Requires-Dist: transformers
|
|
30
|
+
Requires-Dist: transformers<5.0.0,>=4.36.0
|
|
31
31
|
Requires-Dist: trl>=0.7.0
|
|
32
32
|
Requires-Dist: typer<0.21.0,>=0.9.0
|
|
33
33
|
Provides-Extra: audio
|
|
@@ -100,6 +100,7 @@ Description-Content-Type: text/markdown
|
|
|
100
100
|
</p>
|
|
101
101
|
|
|
102
102
|
<p align="center">
|
|
103
|
+
<a href="https://trysoup.dev">Website</a> ·
|
|
103
104
|
<a href="#quick-start">Quick Start</a> ·
|
|
104
105
|
<a href="#features">Features</a> ·
|
|
105
106
|
<a href="#data-tools">Data Tools</a> ·
|
|
@@ -115,6 +116,7 @@ Description-Content-Type: text/markdown
|
|
|
115
116
|
<img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="Apache-2.0 License">
|
|
116
117
|
<a href="https://github.com/MakazhanAlpamys/Soup/actions"><img src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/MakazhanAlpamys/65fdc943f85f3b2c46ecddb415c2b779/raw/soup_tests.json" alt="Tests"></a>
|
|
117
118
|
<a href="https://github.com/MakazhanAlpamys/Soup/actions"><img src="https://github.com/MakazhanAlpamys/Soup/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
119
|
+
<a href="https://trysoup.dev"><img src="https://img.shields.io/badge/website-trysoup.dev-blue" alt="Website"></a>
|
|
118
120
|
</p>
|
|
119
121
|
|
|
120
122
|
---
|
|
@@ -129,15 +131,20 @@ soup train
|
|
|
129
131
|
|
|
130
132
|
## What's New
|
|
131
133
|
|
|
132
|
-
Latest highlights only. Full history: [GitHub Releases](https://github.com/MakazhanAlpamys/Soup/releases).
|
|
133
134
|
|
|
134
|
-
|
|
135
|
+
Latest highlights only. Full history: [GitHub Releases](https://github.com/MakazhanAlpamys/Soup/releases).
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
-
|
|
139
|
-
- **
|
|
140
|
-
-
|
|
137
|
+
**v0.40.2 — Quick polish + carry-overs**: closes 3 originally-scheduled GitHub issues (#36 eval-gate dashboard row, #50 smarter `--hf-resume`, #51 custom HF Space templates) plus 7 v0.40.1 long-tail UX papercuts.
|
|
138
|
+
|
|
139
|
+
- **`--hf-resume` prefers local newer** — `prepare_hf_resume` checks the highest local `checkpoint-N` against the highest remote branch and skips the snapshot download when the local copy is newer or equal. Saves bandwidth and never overwrites a fresher local checkpoint with stale Hub state.
|
|
140
|
+
- **Eval-gate dashboard row** — pure formatter `format_gate_row(state)` lives in `soup_cli/monitoring/display.py` and renders a one-liner like `Gate: helpfulness 7.8 ✓ | math 0.82 ✗ (-0.06) | STOP` for the live training panel. Hidden when eval-gate is disabled.
|
|
141
|
+
- **`soup deploy hf-space --template-dir <path>`** — supply your own `app.py` + `README.md` (+ optional `requirements.txt`) instead of the built-in `gradio-chat` / `streamlit-chat`. Containment-checked, repo-id substitution validated *before* render, 256 KB cap per file, symlinks rejected.
|
|
142
|
+
- **`soup quickstart --output DIR`** — route data, config, and run dir under any directory you choose (default keeps backwards-compat in cwd).
|
|
143
|
+
- **`soup runs --cwd-only`** — restrict the listing to runs whose `output_dir` is under the current directory; the global `~/.soup/experiments.db` view is still default.
|
|
144
|
+
- **`soup infer` / `soup bench` accept HF ids** — when the local model path is missing AND the value isn't path-like (no `./`, `/`, `~`, `C:\`), it falls through to a HuggingFace download via `transformers.from_pretrained`. Path-like-but-missing surfaces a friendly `FileNotFoundError`.
|
|
145
|
+
- **CLI flag aliases** — `data filter --min-coherence` (alias for `--coherence`); `data split --train` accepted (informational, train is the implicit remainder); `data register / unregister` accept positional `<name>` and `<path>` alongside the `--name` / `--path` options, with conflict detection.
|
|
146
|
+
- **`--log-level` plumbing complete** — `apply_logging_level` now sets the root logger so third-party libraries (transformers / peft / trl) actually respect QUIET and DEBUG. The four tiers no longer produce byte-identical output.
|
|
147
|
+
- **+36 net new tests** across the new helpers, security-fix follow-ups (path-containment in `register_data`, `bench.py`, `infer.py --output`, symlink-reject in custom Space templates), and Windows cross-drive edge cases.
|
|
141
148
|
|
|
142
149
|
## Why Soup?
|
|
143
150
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
+
<a href="https://trysoup.dev">Website</a> ·
|
|
12
13
|
<a href="#quick-start">Quick Start</a> ·
|
|
13
14
|
<a href="#features">Features</a> ·
|
|
14
15
|
<a href="#data-tools">Data Tools</a> ·
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
<img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="Apache-2.0 License">
|
|
25
26
|
<a href="https://github.com/MakazhanAlpamys/Soup/actions"><img src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/MakazhanAlpamys/65fdc943f85f3b2c46ecddb415c2b779/raw/soup_tests.json" alt="Tests"></a>
|
|
26
27
|
<a href="https://github.com/MakazhanAlpamys/Soup/actions"><img src="https://github.com/MakazhanAlpamys/Soup/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
28
|
+
<a href="https://trysoup.dev"><img src="https://img.shields.io/badge/website-trysoup.dev-blue" alt="Website"></a>
|
|
27
29
|
</p>
|
|
28
30
|
|
|
29
31
|
---
|
|
@@ -38,15 +40,20 @@ soup train
|
|
|
38
40
|
|
|
39
41
|
## What's New
|
|
40
42
|
|
|
41
|
-
Latest highlights only. Full history: [GitHub Releases](https://github.com/MakazhanAlpamys/Soup/releases).
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
Latest highlights only. Full history: [GitHub Releases](https://github.com/MakazhanAlpamys/Soup/releases).
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
- **
|
|
49
|
-
-
|
|
46
|
+
**v0.40.2 — Quick polish + carry-overs**: closes 3 originally-scheduled GitHub issues (#36 eval-gate dashboard row, #50 smarter `--hf-resume`, #51 custom HF Space templates) plus 7 v0.40.1 long-tail UX papercuts.
|
|
47
|
+
|
|
48
|
+
- **`--hf-resume` prefers local newer** — `prepare_hf_resume` checks the highest local `checkpoint-N` against the highest remote branch and skips the snapshot download when the local copy is newer or equal. Saves bandwidth and never overwrites a fresher local checkpoint with stale Hub state.
|
|
49
|
+
- **Eval-gate dashboard row** — pure formatter `format_gate_row(state)` lives in `soup_cli/monitoring/display.py` and renders a one-liner like `Gate: helpfulness 7.8 ✓ | math 0.82 ✗ (-0.06) | STOP` for the live training panel. Hidden when eval-gate is disabled.
|
|
50
|
+
- **`soup deploy hf-space --template-dir <path>`** — supply your own `app.py` + `README.md` (+ optional `requirements.txt`) instead of the built-in `gradio-chat` / `streamlit-chat`. Containment-checked, repo-id substitution validated *before* render, 256 KB cap per file, symlinks rejected.
|
|
51
|
+
- **`soup quickstart --output DIR`** — route data, config, and run dir under any directory you choose (default keeps backwards-compat in cwd).
|
|
52
|
+
- **`soup runs --cwd-only`** — restrict the listing to runs whose `output_dir` is under the current directory; the global `~/.soup/experiments.db` view is still default.
|
|
53
|
+
- **`soup infer` / `soup bench` accept HF ids** — when the local model path is missing AND the value isn't path-like (no `./`, `/`, `~`, `C:\`), it falls through to a HuggingFace download via `transformers.from_pretrained`. Path-like-but-missing surfaces a friendly `FileNotFoundError`.
|
|
54
|
+
- **CLI flag aliases** — `data filter --min-coherence` (alias for `--coherence`); `data split --train` accepted (informational, train is the implicit remainder); `data register / unregister` accept positional `<name>` and `<path>` alongside the `--name` / `--path` options, with conflict detection.
|
|
55
|
+
- **`--log-level` plumbing complete** — `apply_logging_level` now sets the root logger so third-party libraries (transformers / peft / trl) actually respect QUIET and DEBUG. The four tiers no longer produce byte-identical output.
|
|
56
|
+
- **+36 net new tests** across the new helpers, security-fix follow-ups (path-containment in `register_data`, `bench.py`, `infer.py --output`, symlink-reject in custom Space templates), and Windows cross-drive edge cases.
|
|
50
57
|
|
|
51
58
|
## Why Soup?
|
|
52
59
|
|
|
@@ -9,7 +9,8 @@ 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.40.
|
|
12
|
+
- v0.40.2 -- Full support (latest)
|
|
13
|
+
- v0.40.0-v0.40.x -- Full support
|
|
13
14
|
- v0.39.0-0.39.x -- Bug-fix support only
|
|
14
15
|
- v0.38.0-0.38.x -- Bug-fix support only
|
|
15
16
|
- v0.37.x and below -- No support
|
|
@@ -143,6 +144,8 @@ No known critical vulnerabilities in current releases.
|
|
|
143
144
|
- **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)
|
|
144
145
|
- **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.
|
|
145
146
|
- **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`
|
|
147
|
+
- **v0.40.2 — Quick polish + carry-overs**: New `soup_cli/utils/hf_space.py:render_custom_template_dir` enforces `is_under_cwd` containment on the template directory; `validate_repo_id` runs BEFORE `{MODEL_REPO}` substitution (matches v0.29.0 Part F policy); per-file 256 KB cap (matches v0.39.0 Part E template-size policy); only `app.py` / `README.md` / `requirements.txt` are read (closed allowlist — no path-from-user-data). Symlinks rejected via `os.lstat + stat.S_ISLNK` and non-regular files (FIFO / device) also rejected (matches v0.33.0 #22 prune_checkpoints TOCTOU policy) — defends against `<template_dir>/app.py -> /etc/passwd`. `_find_highest_local_checkpoint` reads `output_dir` after caller's `is_under_cwd` validation (in `prepare_hf_resume`) and silently drops non-directories + OSError. `prepare_hf_resume` skips the snapshot download when local `checkpoint-N >= remote checkpoint-N` (saves bandwidth and never overwrites a fresher local checkpoint). `commands/data.py:register_data` containment switched from `Path.resolve() + relative_to()` to shared `is_under_cwd` (Windows 8.3 short-name safety per CLAUDE.md project rule); same fix applied to `commands/bench.py` prompts-file containment. `commands/infer.py:--output` now containment-checked via `is_under_cwd` (late-evaluated after model+input validation so pre-existing `tmp_path` test contracts keep working). `commands/quickstart.py:--output` validates target dir via `is_under_cwd` before `mkdir(parents=True)`; rejects out-of-cwd targets with friendly message. `commands/runs.py:_filter_runs_by_cwd` uses `os.path.realpath + commonpath`, catches `(ValueError, OSError)` so cross-drive paths on Windows (`D:\runs` vs `C:\project`) drop silently rather than crash. `monitoring/display.py:format_gate_row` uses explicit `task.get("passed") is True` so a missing `"passed"` field renders neutrally instead of as a false-y red ✗. `commands/infer.py:_resolve_model_source` heuristic for HF-id-vs-local-path: only falls through to HF when value is NOT path-like (no `./`, `/`, `\\`, `~`, no Windows drive letter, non-empty); path-like-but-missing raises `FileNotFoundError` so users see actionable errors instead of confusing HF download attempts. Known limitation: custom HF Space templates always create the Space with `space_sdk="gradio"` regardless of the supplied `app.py` (no `--sdk` flag in this release; combine `--template streamlit-chat` with the inline registry for Streamlit Spaces). Tracked for v0.40.3+.
|
|
148
|
+
- **v0.40.1 — QA Hardening**: `soup_cli/utils/encoding.force_utf8_stdio` reconfigures Windows stdout/stderr to UTF-8 before any Rich Console is constructed; `os.environ.setdefault("PYTHONIOENCODING", "utf-8")` preserves user override; `(OSError, ValueError, AttributeError)` swallowed on detached streams; POSIX no-op. `SoupConfig._remap_root_level_misplaced_keys` (model_validator, mode='before') migrates root-level `lora:` into `training.lora` so nested validators (including `lora.init_strategy: Literal["random","pissa","olora"]`) actually fire — closes a footgun where the misplaced key was silently dropped. Caller's dict is never mutated (shallow-copy policy mirroring v0.33.0 #47 / v0.40.0 Part B). `PreferenceTrainerWrapper._build_multi_objective` replaces the v0.40.0 `NotImplementedError` stub with a primary-loss approximation; `validate_weight_compat` rejects BCO mixed with paired losses at runtime (data-format incompatible). `combine_losses` rejects empty weights, propagates NaN loudly (no silent zeroing), and rejects `bool` weight values (matches v0.30.0 `Candidate` policy). `_probe_cache_param_count` rejects empty / null-byte model names before path construction (mirrors v0.26.0 registry / v0.39.0 ReLoRAPolicy policy). `commands/doctor` flags `transformers ≥ 5.0.0` as INCOMPATIBLE via `_MAX_EXCLUSIVE` table; `_version_ge` parses leading-int chunks so `5.0.0.dev0` correctly trips the cap. `_detect_gpu_hw_without_torch_cuda` calls `nvidia-smi` via argv list (no shell), 5s timeout, `OSError` / `TimeoutExpired` caught; GPU label from `nvidia-smi` stdout is `rich.markup.escape`d before embedding in Rich-markup string (a real GPU name like `NVIDIA Quadro [T4]` cannot break or inject markup). `_detect_dual_python_interpreters` uses `os.path.realpath` (not `Path.resolve()`) for Windows 8.3 short-name compat. `_pick_quickstart_model` swaps TinyLlama-1.1B → SmolLM2-135M when `total_memory ≤ 6 GB` (prevents step-0 OOM on RTX 3050 4 GB / similar). `_live_lr_sweep_from_config` switched broken `load_local` import to `load_raw_data` (previously always silently fell back to a static placeholder curve). `commands/migrate` rejects `.jsonl` input (with first-line `{` sniff) with exit-2 friendly error; `.jsonl`-only suffix gate prevents false-positives on `.ipynb` notebooks. `commands/eval custom -o` is now honored independently of `--attach-to-registry`; loop-shadow regression where `output = generate_fn(...)` overwrote the CLI option fixed (variable renamed to `response`). `_load_jsonl` switched from `utf-8` to `utf-8-sig` so PowerShell `Out-File -Encoding utf8`-produced JSONL no longer fails first-row parse. Known limitation: `--trust-remote-code` opt-in surface still excludes 10 non-SFT trainers + 5 commands (v0.36.0 #63 carry-over).
|
|
146
149
|
- **v0.40.0 — Preference Variety**: New `task='bco'` (Binary Classifier Optimization) and `task='preference'` (unified dispatcher). New schema fields: `bco_beta` (gt=0), `preference_loss: Literal[dpo,simpo,orpo,ipo,bco]|None`, `preference_loss_weights: Optional[Dict[str,float]]`, `dpo_beta_schedule: Literal[linear,cosine,exponential]|None`, `dpo_beta_end: float, gt=0|None`, `dpo_ref_regen_epochs: int [1,1000]|None`. Cross-validators: `_validate_preference_dispatcher` rejects setting either `preference_loss` or `preference_loss_weights` outside `task='preference'` (closes ordering-dependency between Part B/D validators); `_validate_dpo_variants_supported_tasks` gates β-schedule + ref-regen to DPO-family tasks (`dpo`, `ipo`, or `preference` + `preference_loss in {dpo, ipo}`); rejected on mlx backend with distinct error message (matches v0.34.0 distinct-reason policy); `_validate_preference_loss_weights` enforces 2–5 entries (single-entry rejected with actionable message pointing at scalar `preference_loss`), key allowlist `{dpo, simpo, orpo, ipo, bco}`, explicit null-byte rejection on keys (matches v0.39.0 rank_pattern policy), per-value bounds `(0, 1]`, weights must sum to 1.0 (±1e-6), mutually exclusive with scalar `preference_loss`, rejected on mlx backend. `compute_beta_at_step` rejects `bool` on `step` and `total_steps` (project bool-as-int policy from v0.30.0). `BetaScheduleCallback` resolves `total_steps` lazily in `on_train_begin` so the schedule sees the real `state.max_steps` populated by HF Trainer (closes a first-cut silent-no-op bug where total_steps=0 emitted beta_end for every step). `RefModelRegenCallback._regenerate` uses `strict=True` on `load_state_dict` and logs at WARNING on mismatch (closes a first-cut silent partial-copy hazard where strict=False could produce a hybrid old-base + new-LoRA reference); epoch 0 regen suppressed (avoids copying untrained student); trainer `.beta` assignment swallow narrowed to `AttributeError` only. `PreferenceTrainerWrapper._make_inner_cfg` uses `model_copy` (not `model_dump`+`model_validate`) so re-validation never sees an inconsistent intermediate state and the caller's `cfg` is never mutated (mirrors v0.33.0 #47 immutability policy). `_split_dpo_rows_to_bco` skipped-row count emitted at DEBUG so production silent-degradation is inspectable (mirrors v0.33.0 #47 CrossDocCollator policy). Multi-objective live runtime weighted-loss combination is deferred to v0.40.1: `PreferenceTrainerWrapper.setup` raises `NotImplementedError` with a friendly message naming the deferred-version follow-up (mirrors v0.27.0 MII / v0.37.0 multipack / v0.38.0 quant menu / v0.39.0 ReLoRA stub-then-live pattern). Known limitation: `BCOTrainerWrapper._setup_transformers` still hardcodes `trust_remote_code=True` (v0.36.0 #63 known-gap family carry-over across non-SFT trainers).
|
|
147
150
|
- **v0.39.0 — LoRA Quality**: `LoraConfig.init_strategy: Literal["random","pissa","olora"]` rejects unknown strategies; PiSSA + DoRA / VeRA combinations rejected at config-load. `model_validator(mode="before")` aligns `use_olora=True` → `init_strategy="olora"` via dict-copy (no caller mutation; matches v0.33.0 #47 immutability policy). `rank_pattern`/`alpha_pattern: Optional[Dict[str, int]]` capped at 256 keys × value (0, 1024], rejects `bool` (subclass of `int` — matches v0.30.0 `Candidate` policy), null bytes in keys, empty keys; cross-validator rejects with `use_vera=True`. `ReLoRAPolicy` is `@dataclass(frozen=True)` (post-construction mutation raises `FrozenInstanceError`); bounds: `steps ∈ [1, 1e7]`, `warmup_ratio ∈ [0, 1]`, `prune_ratio ∈ (0, 1)` (strict — prevents zero-everything footgun). `magnitude_prune_tensor` strict `0 < prune_ratio < 1` rejection, non-Tensor input raises `TypeError`, empty / single-element tensor short-circuits (avoids `kthvalue(_, 0)` runtime crash). `_validate_relora_supported_tasks` cross-validator rejects `relora_steps` with `task != "sft"` and `backend=mlx` with distinct error messages (matches v0.34.0 distinct-reason policy); multi-trainer expansion deferred to v0.39.1. `is_gemma4_model` uses a word-boundary regex (`(?:^|[^a-z0-9])gemma-?4(?:[^a-z0-9]|$)`) so `"ungemma4ed"` / `"my-gemma4ish"` no longer over-match; null-byte rejection on `model_name`. `apply_gemma4_clippable_patch` weight-copy fallback logs at DEBUG instead of silent random-init; the patch is gated by `is_gemma4_model(cfg.base)` in `sft.py` before invocation so non-Gemma4 trainings never traverse the module tree. `apply_surgical_patches` rejects empty / null-byte `model_name` with `ValueError`. `templates/load_template` containment: filename re-validated via `_validate_name` (rejects `..`/`/`/`\\`/null/empty); `os.path.realpath + os.path.commonpath` containment check on the resolved path against `_templates_dir()` so a tampered `manifest.json` cannot read files outside the package directory (mirrors v0.26.0 registry policy). Tampered-manifest `ValueError` from `_validate_name` caught and falls back to inline (no propagating exception). 256 KB file-size cap. Inline `TEMPLATES` carries an explicit deprecation comment pointing at the canonical YAML registry; `tests/test_templates_yaml.py` asserts byte-equality of all 16 inline ↔ YAML pairs to prevent silent drift. Planned removal: v0.41.0+.
|
|
148
151
|
- **v0.38.0 — Quant Menu**: `TrainingConfig.quantization` Literal extended with `gptq` / `awq` / `hqq:1bit`..`hqq:8bit` (no `hqq:7bit` — HQQ doesn't support it) / `aqlm` / `eetq` / `mxfp4` / `fp8`; Pydantic rejects every other string at config-load. `validate_gptq_checkpoint` and `validate_awq_checkpoint` probe local paths for `quantize_config.json` / `quant_config.json`; HF repo IDs fall through; null-byte rejection + non-string `TypeError` on the ref. `_validate_prequantized_no_qat` rejects every pre-quantized format combined with `quantization_aware` (int8 QAT or `'fp8'`) — pre-quantized weights carry their own scale and QAT/FP8 prepare would silently corrupt them (mirrors LlamaFactory `quantization.py:117/199/211`). `_validate_bnb_quant_storage_only_with_4bit` rejects `bnb_4bit_quant_storage` on every non-BNB-4bit format (silent no-op otherwise); allowed dtypes: `Literal["uint8", "float16", "bfloat16", "float32"]`. `_validate_quant_menu_supported_tasks` restricts the new formats to `task='sft'` on `backend='transformers'` in v0.38.0 with distinct MLX-backend vs unsupported-task error messages (matches v0.34.0 distinct-reason policy). `check_quant_distributed_compat` hard-fails HQQ/EETQ/AQLM × {FSDP, ZeRO-3} (sourced from LlamaFactory `quantization.py:199/211` plus AQLM dequant constraints); warning-tier (not error) for BNB-4bit + FSDP without `bnb_4bit_quant_storage` so users see the silent perf cliff; unknown `quantization` raises `ValueError` (no silent pass) and the check is wired into `commands/train.py` startup. `parse_hqq_bits` rejects unsupported bit-rates and malformed `hqq:` strings before any kernel build.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "soup-cli"
|
|
7
|
-
version = "0.40.
|
|
7
|
+
version = "0.40.2"
|
|
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"
|
|
@@ -27,7 +27,7 @@ dependencies = [
|
|
|
27
27
|
"pydantic>=2.0.0",
|
|
28
28
|
"pyyaml>=6.0",
|
|
29
29
|
"torch>=2.0.0",
|
|
30
|
-
"transformers>=4.36.0",
|
|
30
|
+
"transformers>=4.36.0,<5.0.0",
|
|
31
31
|
"peft>=0.7.0",
|
|
32
32
|
"trl>=0.7.0",
|
|
33
33
|
"datasets>=2.14.0",
|
|
@@ -91,12 +91,65 @@ _MODEL_SIZE_RE = re.compile(r"(\d+(?:\.\d+)?)\s*[Bb]")
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def _guess_params_from_name(name: str) -> float:
|
|
94
|
-
"""Extract the parameter count (in billions) from a model name.
|
|
94
|
+
"""Extract the parameter count (in billions) from a model name.
|
|
95
|
+
|
|
96
|
+
v0.40.1 Part C / C3 — fall back to **1B** (not 7B) when the name has no
|
|
97
|
+
embedded size hint. The previous 7.0 default made tiny models like
|
|
98
|
+
``tiny-gpt2`` (5 MB) fail VRAM-budget checks on machines that could
|
|
99
|
+
trivially train them. We also recognise the ``-Mm`` (millions) format
|
|
100
|
+
used by SmolLM2 / Phi-3 family. Probes for a local safetensors index
|
|
101
|
+
(cached HF snapshot) before falling back.
|
|
102
|
+
"""
|
|
95
103
|
match = _MODEL_SIZE_RE.search(name)
|
|
96
104
|
if match:
|
|
97
105
|
return float(match.group(1))
|
|
98
|
-
#
|
|
99
|
-
|
|
106
|
+
# Recognise <N>m / <N>M for sub-billion models (e.g. SmolLM2-135M,
|
|
107
|
+
# tiny-gpt2). Convert to billions.
|
|
108
|
+
m_match = re.search(r"(\d+(?:\.\d+)?)\s*[Mm](?![a-zA-Z])", name)
|
|
109
|
+
if m_match:
|
|
110
|
+
return float(m_match.group(1)) / 1000.0
|
|
111
|
+
# Probe local HF cache for safetensors index size if we can.
|
|
112
|
+
cache_size = _probe_cache_param_count(name)
|
|
113
|
+
if cache_size is not None:
|
|
114
|
+
return cache_size
|
|
115
|
+
# Conservative default — small assumption (yellow advisory should fire).
|
|
116
|
+
return 1.0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _probe_cache_param_count(name: str) -> Optional[float]:
|
|
120
|
+
"""Best-effort: read parameter count from cached safetensors index.
|
|
121
|
+
|
|
122
|
+
Looks at ``~/.cache/huggingface/hub/models--<owner>--<repo>/snapshots/*/
|
|
123
|
+
model.safetensors.index.json`` and returns ``total_size / 4 / 1e9`` (fp32
|
|
124
|
+
bytes per param). Returns ``None`` if not found.
|
|
125
|
+
|
|
126
|
+
v0.40.1 review fix — reject empty / null-byte names (project policy
|
|
127
|
+
mirroring v0.26.0 registry / v0.39.0 ReLoRAPolicy) before constructing
|
|
128
|
+
the cache path.
|
|
129
|
+
"""
|
|
130
|
+
if not isinstance(name, str) or not name or "\x00" in name:
|
|
131
|
+
return None
|
|
132
|
+
try:
|
|
133
|
+
from pathlib import Path as _Path
|
|
134
|
+
|
|
135
|
+
owner_repo = name.replace("/", "--")
|
|
136
|
+
cache = _Path.home() / ".cache" / "huggingface" / "hub" / f"models--{owner_repo}"
|
|
137
|
+
if not cache.is_dir():
|
|
138
|
+
return None
|
|
139
|
+
for idx in cache.rglob("model.safetensors.index.json"):
|
|
140
|
+
try:
|
|
141
|
+
import json as _json
|
|
142
|
+
data = _json.loads(idx.read_text(encoding="utf-8"))
|
|
143
|
+
total_bytes = data.get("metadata", {}).get("total_size")
|
|
144
|
+
if isinstance(total_bytes, (int, float)) and total_bytes > 0:
|
|
145
|
+
# Assume fp32 storage (4 bytes/param) — generous upper
|
|
146
|
+
# bound; bf16/fp16 cuts it in half.
|
|
147
|
+
return float(total_bytes) / 4.0 / 1e9
|
|
148
|
+
except (OSError, ValueError):
|
|
149
|
+
continue
|
|
150
|
+
except Exception: # noqa: BLE001
|
|
151
|
+
return None
|
|
152
|
+
return None
|
|
100
153
|
|
|
101
154
|
|
|
102
155
|
def analyze_model(name: str, params_b: Optional[float] = None) -> ModelProfile:
|
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
# UTF-8 stdio bootstrap (v0.40.1 Part A) — must run before any Rich console
|
|
6
|
+
# is constructed. On Windows, reconfigures sys.stdout/stderr to UTF-8 so β /
|
|
7
|
+
# ✓ / box-drawing chars don't crash with UnicodeEncodeError on cp1251/cp1252.
|
|
8
|
+
# POSIX: no-op.
|
|
9
|
+
from soup_cli.utils.encoding import force_utf8_stdio
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
force_utf8_stdio()
|
|
12
|
+
_utf8_bootstrap_done = True
|
|
13
|
+
|
|
14
|
+
import typer # noqa: E402
|
|
15
|
+
from rich.console import Console # noqa: E402
|
|
16
|
+
|
|
17
|
+
from soup_cli import __version__ # noqa: E402
|
|
18
|
+
from soup_cli.commands import ( # noqa: E402
|
|
10
19
|
adapters,
|
|
11
20
|
autopilot,
|
|
12
21
|
bench,
|
|
@@ -34,15 +43,15 @@ from soup_cli.commands import (
|
|
|
34
43
|
train,
|
|
35
44
|
ui,
|
|
36
45
|
)
|
|
37
|
-
from soup_cli.commands import doctor as doctor_cmd
|
|
38
|
-
from soup_cli.commands import quickstart as quickstart_cmd
|
|
39
|
-
from soup_cli.commands import (
|
|
46
|
+
from soup_cli.commands import doctor as doctor_cmd # noqa: E402
|
|
47
|
+
from soup_cli.commands import quickstart as quickstart_cmd # noqa: E402
|
|
48
|
+
from soup_cli.commands import ( # noqa: E402
|
|
40
49
|
tui as tui_cmd,
|
|
41
50
|
)
|
|
42
|
-
from soup_cli.commands import (
|
|
51
|
+
from soup_cli.commands import ( # noqa: E402
|
|
43
52
|
why as why_cmd,
|
|
44
53
|
)
|
|
45
|
-
from soup_cli.utils.constants import GITHUB_URL
|
|
54
|
+
from soup_cli.utils.constants import GITHUB_URL # noqa: E402
|
|
46
55
|
|
|
47
56
|
console = Console()
|
|
48
57
|
|
|
@@ -217,7 +226,11 @@ def main(
|
|
|
217
226
|
"""Soup — fine-tune LLMs in one command."""
|
|
218
227
|
global _verbose, _log_level
|
|
219
228
|
_verbose = verbose
|
|
220
|
-
from soup_cli.utils.log_level import
|
|
229
|
+
from soup_cli.utils.log_level import (
|
|
230
|
+
apply_logging_level,
|
|
231
|
+
parse_log_level,
|
|
232
|
+
setup_logging,
|
|
233
|
+
)
|
|
221
234
|
|
|
222
235
|
try:
|
|
223
236
|
tier = parse_log_level(log_level)
|
|
@@ -226,6 +239,10 @@ def main(
|
|
|
226
239
|
raise typer.Exit(code=2) from exc
|
|
227
240
|
_log_level = tier.value
|
|
228
241
|
setup_logging(tier)
|
|
242
|
+
# v0.40.2 N1/G2: also push the tier into the root logger so third-party
|
|
243
|
+
# libraries (transformers / peft / trl) respect QUIET / DEBUG. Without
|
|
244
|
+
# this, all four levels were producing nearly-identical output.
|
|
245
|
+
apply_logging_level(tier)
|
|
229
246
|
|
|
230
247
|
|
|
231
248
|
def run():
|
|
@@ -43,13 +43,24 @@ def bench(
|
|
|
43
43
|
"""Run an inference benchmark (speed and memory) on a loaded model."""
|
|
44
44
|
import torch
|
|
45
45
|
|
|
46
|
-
from soup_cli.commands.infer import _generate, _load_model
|
|
46
|
+
from soup_cli.commands.infer import _generate, _load_model, _resolve_model_source
|
|
47
47
|
from soup_cli.utils.gpu import detect_device
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
# Resolve local-path-or-HF-id (#N7).
|
|
50
|
+
try:
|
|
51
|
+
model_kind, model_ref = _resolve_model_source(model)
|
|
52
|
+
except FileNotFoundError as exc:
|
|
53
|
+
console.print(
|
|
54
|
+
f"[red]{exc}[/]\n"
|
|
55
|
+
"[dim]If you meant a HuggingFace repo, use the form "
|
|
56
|
+
"'owner/repo-name' (no leading './').[/]"
|
|
57
|
+
)
|
|
58
|
+
raise typer.Exit(1) from exc
|
|
59
|
+
model_path = Path(model_ref)
|
|
60
|
+
if model_kind == "hf":
|
|
61
|
+
console.print(
|
|
62
|
+
f"[dim]Local path not found; treating {model_ref!r} as a HF repo id.[/]"
|
|
63
|
+
)
|
|
53
64
|
|
|
54
65
|
device, _ = detect_device()
|
|
55
66
|
|
|
@@ -61,15 +72,17 @@ def bench(
|
|
|
61
72
|
|
|
62
73
|
if prompts_file:
|
|
63
74
|
import json
|
|
64
|
-
|
|
75
|
+
import os as _os
|
|
65
76
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
from soup_cli.utils.paths import is_under_cwd
|
|
78
|
+
|
|
79
|
+
if not is_under_cwd(prompts_file):
|
|
69
80
|
console.print(
|
|
70
|
-
|
|
81
|
+
"[red]Security Error:[/] Prompts file must stay under the "
|
|
82
|
+
"current working directory."
|
|
71
83
|
)
|
|
72
84
|
raise typer.Exit(1)
|
|
85
|
+
p_path = Path(_os.path.realpath(prompts_file))
|
|
73
86
|
|
|
74
87
|
if not p_path.is_file():
|
|
75
88
|
console.print(f"[red]Prompts file not found:[/] {p_path}")
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
import random
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
8
9
|
|
|
9
10
|
import typer
|
|
10
11
|
from rich.console import Console
|
|
@@ -325,7 +326,7 @@ def filter_data(
|
|
|
325
326
|
help="Max perplexity threshold (rows above this are removed)",
|
|
326
327
|
),
|
|
327
328
|
coherence: float = typer.Option(
|
|
328
|
-
None, "--coherence",
|
|
329
|
+
None, "--coherence", "--min-coherence",
|
|
329
330
|
help="Min coherence threshold 0.0-1.0 (rows below this are removed)",
|
|
330
331
|
),
|
|
331
332
|
perplexity_model: str = typer.Option(
|
|
@@ -746,8 +747,11 @@ def sample_data(
|
|
|
746
747
|
sampled = _sample_random(data, sample_count, seed=seed)
|
|
747
748
|
|
|
748
749
|
# Resolve output path (with path traversal protection on explicit --output)
|
|
750
|
+
# v0.40.1 Part E — include the strategy in the default filename so
|
|
751
|
+
# successive `random` / `diverse` / `hard` runs don't silently overwrite
|
|
752
|
+
# each other.
|
|
749
753
|
if output is None:
|
|
750
|
-
out_path = file_path.parent / f"{file_path.stem}
|
|
754
|
+
out_path = file_path.parent / f"{file_path.stem}_sampled_{strategy}.jsonl"
|
|
751
755
|
else:
|
|
752
756
|
out_path = Path(output).resolve()
|
|
753
757
|
cwd = Path.cwd().resolve()
|
|
@@ -777,6 +781,13 @@ def split_data(
|
|
|
777
781
|
None, "--test",
|
|
778
782
|
help="Test split: percentage (default) or absolute count (with --absolute)",
|
|
779
783
|
),
|
|
784
|
+
train: int = typer.Option(
|
|
785
|
+
None, "--train",
|
|
786
|
+
help=(
|
|
787
|
+
"Train split (informational; the train remainder is implied by "
|
|
788
|
+
"--val + --test). Accepted for command parity."
|
|
789
|
+
),
|
|
790
|
+
),
|
|
780
791
|
absolute: bool = typer.Option(
|
|
781
792
|
False, "--absolute",
|
|
782
793
|
help="Treat --val/--test as absolute sample counts instead of percentages",
|
|
@@ -1458,56 +1469,104 @@ def _load_augment_provider(provider: str, rpm: int):
|
|
|
1458
1469
|
|
|
1459
1470
|
@app.command(name="register")
|
|
1460
1471
|
def register_data(
|
|
1461
|
-
|
|
1462
|
-
|
|
1472
|
+
name_arg: Optional[str] = typer.Argument(
|
|
1473
|
+
None, help="Dataset name (positional alternative to --name)",
|
|
1474
|
+
),
|
|
1475
|
+
path_arg: Optional[str] = typer.Argument(
|
|
1476
|
+
None, help="Path to dataset file (positional alternative to --path)",
|
|
1477
|
+
),
|
|
1478
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Dataset name"),
|
|
1479
|
+
path: Optional[str] = typer.Option(
|
|
1480
|
+
None, "--path", "-p", help="Path to dataset file"
|
|
1481
|
+
),
|
|
1463
1482
|
fmt: str = typer.Option(
|
|
1464
1483
|
"auto", "--format", "-f",
|
|
1465
1484
|
help="Dataset format: alpaca, sharegpt, chatml, dpo, kto, auto",
|
|
1466
1485
|
),
|
|
1467
1486
|
):
|
|
1468
|
-
"""Register a local dataset by name for use in soup.yaml.
|
|
1487
|
+
"""Register a local dataset by name for use in soup.yaml.
|
|
1488
|
+
|
|
1489
|
+
Accepts both ``--name X --path Y`` and positional ``X Y``.
|
|
1490
|
+
"""
|
|
1469
1491
|
from soup_cli.utils.registry import register_dataset
|
|
1470
1492
|
|
|
1471
|
-
#
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1493
|
+
# Resolve positional vs option, with conflict detection
|
|
1494
|
+
final_name = name if name is not None else name_arg
|
|
1495
|
+
final_path = path if path is not None else path_arg
|
|
1496
|
+
if final_name is None or final_path is None:
|
|
1497
|
+
console.print(
|
|
1498
|
+
"[red]Provide both name and path "
|
|
1499
|
+
"(positional `<name> <path>` or `--name --path`).[/]"
|
|
1500
|
+
)
|
|
1501
|
+
raise typer.Exit(2)
|
|
1502
|
+
if name is not None and name_arg is not None and name != name_arg:
|
|
1503
|
+
console.print("[red]Conflict: --name and positional name differ.[/]")
|
|
1504
|
+
raise typer.Exit(2)
|
|
1505
|
+
if path is not None and path_arg is not None and path != path_arg:
|
|
1506
|
+
console.print("[red]Conflict: --path and positional path differ.[/]")
|
|
1507
|
+
raise typer.Exit(2)
|
|
1508
|
+
|
|
1509
|
+
# Path traversal protection — use os.path.realpath + commonpath
|
|
1510
|
+
# (project standard; Windows 8.3 short-name safe).
|
|
1511
|
+
import os as _os
|
|
1512
|
+
|
|
1513
|
+
from soup_cli.utils.paths import is_under_cwd
|
|
1514
|
+
|
|
1515
|
+
if not is_under_cwd(final_path):
|
|
1477
1516
|
console.print(
|
|
1478
1517
|
"[red]Dataset path must be under the current working directory.[/]"
|
|
1479
1518
|
)
|
|
1480
1519
|
raise typer.Exit(1)
|
|
1520
|
+
resolved = Path(_os.path.realpath(final_path))
|
|
1481
1521
|
|
|
1482
1522
|
registry_path = _get_registry_path()
|
|
1483
1523
|
|
|
1484
1524
|
try:
|
|
1485
|
-
register_dataset(
|
|
1525
|
+
register_dataset(final_name, str(resolved), fmt, registry_path=registry_path)
|
|
1486
1526
|
except ValueError as exc:
|
|
1487
1527
|
console.print(f"[red]{exc}[/]")
|
|
1488
1528
|
raise typer.Exit(1)
|
|
1489
1529
|
|
|
1490
1530
|
console.print(
|
|
1491
|
-
f"[green]Registered dataset '[bold]{
|
|
1492
|
-
f" Path: {
|
|
1531
|
+
f"[green]Registered dataset '[bold]{final_name}[/bold]'[/]\n"
|
|
1532
|
+
f" Path: {final_path}\n"
|
|
1493
1533
|
f" Format: {fmt}"
|
|
1494
1534
|
)
|
|
1495
1535
|
|
|
1496
1536
|
|
|
1497
1537
|
@app.command(name="unregister")
|
|
1498
1538
|
def unregister_data(
|
|
1499
|
-
|
|
1539
|
+
name_arg: Optional[str] = typer.Argument(
|
|
1540
|
+
None, help="Dataset name (positional alternative to --name)",
|
|
1541
|
+
),
|
|
1542
|
+
name: Optional[str] = typer.Option(
|
|
1543
|
+
None, "--name", "-n", help="Dataset name to remove"
|
|
1544
|
+
),
|
|
1500
1545
|
):
|
|
1501
|
-
"""Remove a dataset from the local registry.
|
|
1546
|
+
"""Remove a dataset from the local registry.
|
|
1547
|
+
|
|
1548
|
+
Accepts both ``--name X`` and positional ``X``.
|
|
1549
|
+
"""
|
|
1502
1550
|
from soup_cli.utils.registry import unregister_dataset
|
|
1503
1551
|
|
|
1552
|
+
final_name = name if name is not None else name_arg
|
|
1553
|
+
if final_name is None:
|
|
1554
|
+
console.print(
|
|
1555
|
+
"[red]Provide a dataset name "
|
|
1556
|
+
"(positional `<name>` or `--name`).[/]"
|
|
1557
|
+
)
|
|
1558
|
+
raise typer.Exit(2)
|
|
1559
|
+
if name is not None and name_arg is not None and name != name_arg:
|
|
1560
|
+
console.print("[red]Conflict: --name and positional name differ.[/]")
|
|
1561
|
+
raise typer.Exit(2)
|
|
1562
|
+
|
|
1504
1563
|
registry_path = _get_registry_path()
|
|
1505
|
-
removed = unregister_dataset(
|
|
1564
|
+
removed = unregister_dataset(final_name, registry_path=registry_path)
|
|
1506
1565
|
|
|
1507
1566
|
if removed:
|
|
1508
|
-
console.print(f"[green]Removed dataset '{
|
|
1567
|
+
console.print(f"[green]Removed dataset '{final_name}' from registry.[/]")
|
|
1509
1568
|
else:
|
|
1510
|
-
console.print(f"[red]Dataset '{
|
|
1569
|
+
console.print(f"[red]Dataset '{final_name}' not found in registry.[/]")
|
|
1511
1570
|
raise typer.Exit(1)
|
|
1512
1571
|
|
|
1513
1572
|
|
|
@@ -435,6 +435,15 @@ def hf_space(
|
|
|
435
435
|
"-t",
|
|
436
436
|
help=f"Space template: {', '.join(HF_SPACE_TEMPLATES.keys())}",
|
|
437
437
|
),
|
|
438
|
+
template_dir: Optional[str] = typer.Option(
|
|
439
|
+
None,
|
|
440
|
+
"--template-dir",
|
|
441
|
+
help=(
|
|
442
|
+
"Custom template directory (overrides --template). "
|
|
443
|
+
"Must contain app.py + README.md, optionally requirements.txt. "
|
|
444
|
+
"Use {MODEL_REPO} placeholder for substitution."
|
|
445
|
+
),
|
|
446
|
+
),
|
|
438
447
|
private: bool = typer.Option(
|
|
439
448
|
False, "--private", help="Create the Space as private",
|
|
440
449
|
),
|
|
@@ -461,7 +470,7 @@ def hf_space(
|
|
|
461
470
|
except ValueError as exc:
|
|
462
471
|
console.print(f"[red]Invalid --space repo id:[/] {exc}")
|
|
463
472
|
raise typer.Exit(1) from exc
|
|
464
|
-
if template not in HF_SPACE_TEMPLATES:
|
|
473
|
+
if template_dir is None and template not in HF_SPACE_TEMPLATES:
|
|
465
474
|
console.print(
|
|
466
475
|
f"[red]Unknown template: {template}[/]\n"
|
|
467
476
|
f"Available: {', '.join(HF_SPACE_TEMPLATES.keys())}"
|
|
@@ -485,16 +494,28 @@ def hf_space(
|
|
|
485
494
|
|
|
486
495
|
# --- Render template files ---
|
|
487
496
|
try:
|
|
488
|
-
|
|
497
|
+
if template_dir is not None:
|
|
498
|
+
from soup_cli.utils.hf_space import render_custom_template_dir
|
|
499
|
+
files = render_custom_template_dir(template_dir, model_repo=model)
|
|
500
|
+
# Custom templates default to gradio SDK unless requirements
|
|
501
|
+
# imply otherwise; we record gradio for create_repo space_sdk.
|
|
502
|
+
sdk = "gradio"
|
|
503
|
+
else:
|
|
504
|
+
files = render_space_template(template, model_repo=model)
|
|
505
|
+
sdk = HF_SPACE_TEMPLATES[template]["sdk"]
|
|
489
506
|
except ValueError as exc:
|
|
490
507
|
console.print(f"[red]Template render failed:[/] {exc}")
|
|
491
508
|
raise typer.Exit(1) from exc
|
|
509
|
+
except FileNotFoundError as exc:
|
|
510
|
+
console.print(f"[red]Template directory error:[/] {exc}")
|
|
511
|
+
raise typer.Exit(1) from exc
|
|
492
512
|
|
|
513
|
+
template_label = template_dir if template_dir is not None else template
|
|
493
514
|
console.print(
|
|
494
515
|
Panel(
|
|
495
516
|
f"Space: [bold]{space}[/]\n"
|
|
496
517
|
f"Model: [bold]{model}[/]\n"
|
|
497
|
-
f"Template: [bold]{
|
|
518
|
+
f"Template: [bold]{template_label}[/]\n"
|
|
498
519
|
f"Private: [bold]{private}[/]",
|
|
499
520
|
title="Deploy HuggingFace Space",
|
|
500
521
|
)
|
|
@@ -514,7 +535,7 @@ def hf_space(
|
|
|
514
535
|
try:
|
|
515
536
|
api.create_repo(
|
|
516
537
|
repo_id=space, repo_type="space",
|
|
517
|
-
space_sdk=
|
|
538
|
+
space_sdk=sdk,
|
|
518
539
|
private=private, exist_ok=True,
|
|
519
540
|
)
|
|
520
541
|
for in_repo_name, content in files.items():
|