forked from MakazhanAlpamys/Soup
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyproject.toml
More file actions
283 lines (274 loc) · 14.6 KB
/
Copy pathpyproject.toml
File metadata and controls
283 lines (274 loc) · 14.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "soup-cli"
version = "0.73.3"
description = "Fine-tune and post-train LLMs in one command. No SSH, no config hell."
readme = "README.md"
license = "Apache-2.0"
# Upper-bounded on purpose (#358). CI tests 3.10 / 3.11 / 3.12 and nothing above.
# Without a ceiling, pip on 3.13+ resolves torch wheels nobody here has run, and
# the failure is not a Soup error message — it is a loader crash inside c10.dll /
# libc10.so before any Soup code executes, so the user has nothing to act on.
# `tests/test_requires_python_bound.py` derives this bound from the CI matrix, so
# widening one without the other fails the suite.
requires-python = ">=3.10,<3.13"
authors = [
{ name = "Soup Team", email = "team@trysoup.dev" },
]
keywords = ["llm", "fine-tuning", "lora", "qlora", "machine-learning"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
# Core install (`pip install soup-cli`) is intentionally light: the CLI, config
# system, and data tools — no PyTorch. v0.71.0 split the heavy training stack
# (torch / transformers / peft / trl / datasets / bitsandbytes / accelerate)
# into the `[train]` extra below.
dependencies = [
"typer>=0.9.0,<0.21.0",
"rich>=13.0.0",
"pydantic>=2.0.0",
"pyyaml>=6.0",
"huggingface-hub>=0.16.0",
"plotext>=5.2.0",
# `env check` reads declared bounds via packaging (Requirement/marker/version).
# It ships with pip and is pulled in transitively, but `env check` degrades to
# a silent "clean" without it — the wrong direction for a checker — so it is
# declared explicitly (#368 review).
"packaging>=21.0",
]
[project.optional-dependencies]
# v0.71.0 — heavy training stack. `pip install 'soup-cli[train]'` to fine-tune.
# These were core dependencies through v0.70.0; pins are unchanged.
train = [
"torch>=2.0.0",
# THE <5.0.0 CEILING IS LOAD-BEARING. Do not lift it as a precaution: it was set
# 2026-05-08, before transformers 5 existed, which is exactly why it reads like one.
# Measured against transformers 5.15.1 -- 45 tests fail, not one of them in Soup.
#
# transformers 5 changed the return type of a PRIVATE helper that trl imports:
# 4.57.6: _is_package_available("weave") -> False (bool, falsey)
# 5.15.1: _is_package_available("weave") -> (False, None) (tuple, TRUTHY)
# The tuple comes back for present packages too, so the inversion fires on ABSENT
# ones: "not installed" reads as true. trl/import_utils.py derives its optional
# dependency flags from that helper, so the guarded `import weave` runs anyway and
# dpo_trainer / grpo_trainer never import behind the ModuleNotFoundError. 13
# module-level flags in trl 0.19.1 are built this way; weave merely fires first.
#
# The trl boundary is exact, and it is why this bound cannot be lifted alone:
# trl 0.28.0 still imports the private helper; trl 0.29.0 vendors its own
# _is_package_available (importlib.util.find_spec) and is immune. That is ONE
# version above Soup's own `trl<0.29` cap. So the chain is
# transformers<5 <- trl<0.29 <- the BasePairwiseJudge import documented below,
# and lifting any single link achieves nothing.
#
# Consequence: [mlx] needs transformers>=5.0.0 -- mlx-lm's own requirement, only
# restated in that extra -- so [train] and [mlx] cannot co-install, and there is no
# side to concede. Full measurement and the acceptance criteria for lifting this
# bound live in issue #503. Guarded by tests/test_transformers_ceiling_compat.py.
"transformers>=4.36.0,<5.0.0",
"peft>=0.7.0",
# Capped below 0.29. #326 raised this from `<0.27` after migrating the six
# preference trainers (bco, dpo, ipo, kto, orpo, simpo) off the two trl APIs
# that moved. The cap is no longer set by those trainers at all — see the
# 0.29 note at the bottom, which is about `online_dpo`.
#
# trl removed `max_prompt_length` from the preference configs in STAGES
# rather than in one release — which is why a single spot-check gives the
# wrong answer. This table is now verified the only way that settles it, by
# CONSTRUCTING each config with the exact kwargs the wrappers pass:
#
# version dpo kto orpo cpo bco
# 0.14.0 - 0.26.2 yes yes yes yes yes
# 0.27.0 - 0.27.2 yes NO yes yes yes
# 0.28.0 yes NO NO NO NO
# 0.29.0 - 1.9.2 NO NO NO NO NO
#
# There is NO successor field: `max_length` survives on every version and
# absorbs prompt truncation, so the migration is to stop passing the keyword
# to a config that no longer has it, not to rename it. The trainers now ask
# the config class whether it accepts the keyword (trainer/_trl_compat.py)
# instead of comparing versions, because a version table is exactly what was
# wrong twice before. On every trl that still has the field the behaviour is
# byte-identical — measured: the six train to identical losses on 0.26.2,
# 0.28.0 and 1.9.2 on the same fixture.
#
# Separately, trl 0.29.0 dropped ORPOConfig / CPOConfig / BCOConfig (and
# their trainers) from the public `trl` namespace. They were not deleted —
# they live on under `trl.experimental.<algo>` — so those three trainers now
# fall back to that path when the public import is gone.
#
# WHY THE CAP IS 0.29 AND NOT HIGHER: `soup train --task online_dpo` is
# broken on 0.29.x, and the cause is not in these trainers. trl 0.29 moved
# `BasePairwiseJudge` to `trl.experimental.judges` while still accepting
# `judge=`, so the wrapper correctly selects the judge path and then
# `eval/judge.py::_base_pairwise_judge_cls` cannot import the base class.
# trl 1.x drops `judge=` entirely, so the wrapper falls back to
# `reward_funcs=` and online_dpo works again — measured: all nine tasks build
# on 1.9.2, and the six preference tasks train there. Raising the cap past
# 0.29 therefore needs (a) a `trl.experimental.judges` fallback in
# eval/judge.py and (b) validation of the trl surfaces this bump did not
# exercise (serve, GRPO variants, PPO with a real reward model). Shipping
# against a release nobody has validated is not a substitute for that.
#
# The underlying bug was latent, not new: the trl imports live inside
# `setup()`, which no test had ever called on those wrappers, so CI stayed
# green while anyone pip-installing got a broken install. v0.72.4's
# end-to-end preference tests are what surfaced it.
#
# The floor is 0.14.0, not 0.7.0: `setup()` imports GRPOTrainer
# unconditionally and trl first exports it at 0.14.0 (OnlineDPO / KTO / BCO /
# BasePairwiseJudge arrive at 0.11.0; trl 0.7.0 has none of them). Resolvers
# normally pick the newest allowed version, so the impossible floor only bit
# under a constraints file or anyone reading the metadata as a support claim.
"trl>=0.14.0,<0.29",
"datasets>=2.14.0",
"bitsandbytes>=0.41.0",
"accelerate>=0.25.0",
]
# v0.71.0 — convenience meta-extra pulling the main optional stacks.
all = ["soup-cli[train,serve,ui,data,mcp]"]
eval = ["lm-eval>=0.4.0"]
# Aider's normal CLI. Its Polyglot harness is source-only, so `soup eval aider`
# additionally requires the upstream benchmark Docker image documented in
# docs/evaluation.md; the wheel alone cannot supply benchmark/benchmark.py.
aider = ["aider-chat>=0.86.2,<0.87"]
data = ["datasketch>=1.6.0", "scikit-learn>=1.0.0"]
wandb = ["wandb>=0.15.0,<0.18.0"]
# Self-references `[train]` so CI / contributors get the full training stack
# (CI runs `pip install -e ".[dev]"`; without this every test would fail at
# `import torch`).
dev = ["soup-cli[train,mcp,data]", "cryptography>=41.0.0", "reportlab>=4.0.0", "pytest>=7.0", "ruff>=0.1.0", "pytest-cov>=4.0", "httpx>=0.24.0", "mypy>=1.8.0", "pre-commit>=3.5.0"]
ui = ["fastapi>=0.104.0", "uvicorn>=0.24.0"]
serve = ["fastapi>=0.104.0", "uvicorn>=0.24.0"]
serve-fast = ["vllm>=0.4.0", "fastapi>=0.104.0", "uvicorn>=0.24.0"]
generate = ["httpx>=0.24.0"]
deepspeed = ["deepspeed>=0.12.0"]
fast = ["unsloth>=2024.8"]
vision = ["Pillow>=9.0.0"]
qat = ["torchao>=0.4.0"]
liger = ["liger-kernel>=0.3.0"]
ring-attn = ["ring-flash-attn>=0.1.0"]
onnx = ["optimum[onnxruntime]>=1.16.0"]
tensorrt = ["tensorrt_llm>=0.9.0"]
audio = ["librosa>=0.10.0", "soundfile>=0.12.0"]
awq = ["autoawq>=0.2.0"]
gptq = ["auto-gptq>=0.7.0"]
sglang = ["sglang>=0.2.0", "fastapi>=0.104.0", "uvicorn>=0.24.0"]
mlx = [
"mlx>=0.20.0",
"mlx-lm>=0.31.3",
# mlx-lm imports transformers at runtime; keep the standalone contract from
# depending on that transitive requirement remaining undeclared here.
"transformers>=5.0.0",
]
cce = ["cut-cross-entropy>=24.10.0"]
tui = ["textual>=0.50.0"]
# v0.53.8 #89 — bundle MLflow / SwanLab / Trackio for `--tracker` users.
trackers = ["mlflow>=2.0.0", "swanlab>=0.3.0", "trackio>=0.0.1"]
# v0.53.8 #85 — fsspec backends for remote dataset loading (s3 / gs / az / oci).
remote = ["fsspec>=2024.1.0", "s3fs>=2024.1.0", "gcsfs>=2024.1.0", "adlfs>=2024.1.0"]
# v0.53.10 #150 — bundle scikit-optimize so `soup data mix --optimize` runs the
# Bayesian-style loop instead of falling back to the v0.48.0 Dirichlet sampler.
mix = ["scikit-optimize>=0.9.0"]
# v0.53.10 #113 — production-grade data quality: langdetect (language) +
# presidio-analyzer (PII). Llama-Guard-3-1B is documented as a manual recipe
# (license + ~600 MB weight blob too large to bundle by default).
data-pro = ["langdetect>=1.0.9", "presidio-analyzer>=2.2.0"]
# v0.71.2 #179/#185 — ed25519 detached signing for `soup attest` / `soup
# adapters sign`. Pure-offline; Sigstore keyless (OIDC + Fulcio/Rekor network)
# stays infra-blocked and is NOT bundled here.
sign = ["cryptography>=41.0.0"]
# v0.71.3 #181 — reportlab PDF rendering for `soup train --annex-xi *.pdf`.
pdf = ["reportlab>=4.0.0"]
# v0.71.3 #180 — codecarbon offline energy/CO2 measurement for
# `soup train --track-energy` (offline; no IP-geolocation network call).
carbon = ["codecarbon>=2.0.0"]
# v0.71.13 #225/#227 — prompt-program / tool-schema compilers
# (`soup compile` / `soup compile-tools`). Lazy-imported with a friendly
# ImportError; not installed by default (heavy dependency trees).
compile = ["dspy-ai>=2.5.0", "textgrad>=0.1.0", "gepa>=0.0.1"]
# v0.71.18 #16 — serverless cloud GPU training (`soup train --cloud modal`).
# Lazy-imported; only needed for `--cloud-submit` (plan-only render needs no
# dependency). Modal auth is via `modal setup`.
modal = ["modal>=0.60.0"]
# v0.71.28 - `soup mcp serve` MCP server. The official `mcp` python SDK is
# lazy-imported (only src/soup_cli/mcp_server/server.py touches it), so the CLI
# stays light without it. Floor pinned to guard against SDK API churn.
#
# v0.72.3: capped below 2.0. The floor alone was not enough — mcp 2.0.0 removed
# `mcp.shared.memory.create_connected_server_and_client_session` and dropped
# `Server.list_tools`, which broke every `soup mcp serve` round-trip test on all
# nine CI jobs the day it was published. 1.29.0 (the newest 1.x) still exposes
# both, verified directly. Migrating to the 2.x API is its own piece of work,
# and shipping against a major nobody has validated is not a substitute for it.
# v0.73.4 (#296): floor raised from 1.2.0 for the sse / streamable-http
# transports. Measured against the published wheels rather than the
# changelog: `mcp/server/streamable_http_manager.py` first appears in
# 1.8.0 (absent in 1.7.0) and `mcp/server/transport_security.py` in
# 1.10.0 (absent in 1.9.4). `--transport sse` alone would have run on
# the old floor; DNS-rebinding protection would not, and a listener
# whose origin checking silently vanishes on an older SDK is worse than
# a resolver error. No new package: `mcp` already requires starlette,
# uvicorn, sse-starlette and httpx-sse.
#
# v0.73.4 (#322): cap lifted to `<3`. The two removals that forced it are
# bridged rather than pinned around. `build_server` probes the `Server`
# constructor for `on_list_tools` — 2.x registers handlers there, 1.x through
# decorators — and the round-trip tests were rebuilt on
# `create_client_server_memory_streams`, which both majors still ship, instead
# of the 2.x-removed `create_connected_server_and_client_session`. Verified by
# running the MCP suite twice, once per major, not by reading release notes.
# The three modules the sse / streamable-http transports depend on
# (`sse`, `streamable_http_manager`, `transport_security`) all survive in 2.0.0,
# so those needed no change at all. The floor stays 1.10.0 for the reason above.
mcp = ["mcp>=1.10.0,<3"]
[project.scripts]
soup = "soup_cli.cli:run"
[project.urls]
Homepage = "https://github.com/MakazhanAlpamys/Soup"
Repository = "https://github.com/MakazhanAlpamys/Soup"
Issues = "https://github.com/MakazhanAlpamys/Soup/issues"
Discord = "https://discord.gg/8RgVbFA6Zq"
[tool.hatch.build.targets.wheel]
packages = ["src/soup_cli"]
# v0.53.8 #93 — include bundled fixture JSONLs as package data so
# `soup data demo` works in zipapp / namespace-package installs.
# Hatchling's ``packages = ["src/soup_cli"]`` already recurses into the
# package directory, so we use the artifacts directive (NOT
# force-include, which double-shipped the files in v0.53.8 and produced
# a "duplicate filename in local headers" 400 from PyPI upload).
artifacts = [
"src/soup_cli/data/_fixtures/*.jsonl",
"src/soup_cli/data/_fixtures/unlearning/*.jsonl",
"src/soup_cli/data/_fixtures/behavior/*.jsonl",
"src/soup_cli/data/_fixtures/gate/*.jsonl",
]
[tool.ruff]
target-version = "py310"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W"]
[tool.mypy]
# Intentionally lenient: the codebase leans on torch / transformers / peft / trl,
# none of which ship complete stubs. This config surfaces type issues in our own
# code without drowning in third-party `Any`s. The CI `type-check` job is
# non-blocking; tighten incrementally as modules gain annotations.
python_version = "3.10"
files = ["src/soup_cli"]
ignore_missing_imports = true
follow_imports = "silent"
[tool.pytest.ini_options]
testpaths = ["tests"]
markers = [
"smoke: slow smoke tests that download models and run training (run with: pytest -m smoke)",
"unit: fast isolated tests — no subprocess, network, filesystem, or real model load",
"integration: tests that touch real subprocess, SQLite, filesystem, or HTTP",
]
addopts = "-m 'not smoke' --cov=soup_cli --cov-fail-under=77 --cov-report=term-missing:skip-covered"