From 18e863f0160401bab330b662583b6ae5b3a49a16 Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Sun, 5 Jul 2026 00:15:49 +0300 Subject: [PATCH 1/3] feat(deps)!: migrate to transformers 5.x (fix nightly CVE-2026-4372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nightly CI failed 2026-07-02..04 on the supply-chain pip-audit gate: transformers CVE-2026-4372 (critical from_pretrained RCE, fixed in 5.3.0) dropped, and check_pip_audit fails closed on its UNKNOWN OSV severity. The old transformers<5.0.0 pin could not reach the fix. Raise the core floor to transformers>=5.3.0,<6.0.0 and cascade the co-dependencies it requires: torch>=2.4.0, huggingface_hub>=1.3.0,<2.0.0, peft>=0.19.0, accelerate>=1.4.0, datasets>=4.7.0,<6.0.0, trl>=1.0.0, requests>=2.32.2 (trl 1.0 pulls the accelerate>=1.4 / datasets>=4.7 floor; datasets 4.7 pulls requests>=2.32.2). BREAKING CHANGE: drops transformers 4.x and Intel Mac (x86_64) support — the latter has no torch>=2.4 wheel on PyPI. Apple Silicon, Linux and Windows are unaffected. - code: drop safe_serialization=True from the 3 save_pretrained sites (removed in tf5; safetensors is the enforced default). Rename torch_dtype= -> dtype= at the export/synthetic from_pretrained sites (deprecated alias in tf5). Refresh the stale trl-pin comment in trainer.py. - ci: rebuild nightly test-min-deps floor to a co-installable tf5 set. - security: remove BOTH now-inert transformers suppressions from pip_audit_ignores.yaml — CVE-2026-1839 (fixed 5.0.0rc3) and PYSEC-2025-217 / CVE-2025-14929 (OSV last-affected 5.0.0rc0); update supply-chain advisory count to zero transformers (EN+TR). - docs: README prerequisite (torch>=2.4, Intel Mac drop) + installation.md Intel Mac note (EN+TR); CHANGELOG. - bump to 0.9.0rc1. Reviewed via 4 parallel review agents (correctness, dep-resolution, docs/security, conventions); their findings (requests floor, second inert suppression, torch_dtype rename, installation-guide gap) are folded in. Validated locally: 8 stdlib doc/version guards pass; nightly test-latest-deps already green on tf5.13. Full pytest/ruff/pip-audit and the min-deps floor resolution remain CI-gated. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/nightly.yml | 19 +++++-- CHANGELOG.md | 39 ++++++++++++++- README.md | 2 +- docs/reference/supply_chain_security-tr.md | 7 +-- docs/reference/supply_chain_security.md | 7 +-- .../en/getting-started/installation.md | 4 ++ .../tr/getting-started/installation.md | 4 ++ forgelm/export.py | 12 +++-- forgelm/merging.py | 6 ++- forgelm/synthetic.py | 3 +- forgelm/trainer.py | 12 +++-- pyproject.toml | 32 ++++++------ tools/pip_audit_ignores.yaml | 49 +++---------------- 13 files changed, 114 insertions(+), 82 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 97b21978..4e2afa19 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -223,11 +223,22 @@ jobs: # forgelm`` before any test runs. run: | pip install --upgrade pip - # Install TRL first (most restrictive), let pip resolve transitive deps + # transformers-5 migration floor (v0.9.0). Every gate is pinned + # EXPLICITLY so this job tests the true documented minimum — trl puts + # no upper bound on transformers, so an unpinned resolve would silently + # pull the newest 5.x and stop exercising the floor. transformers + # 5.3.0 is the CVE-2026-4372 fix floor and requires torch>=2.4 / + # huggingface_hub>=1.3 / tokenizers>=0.22; trl 1.0.0 pulls the + # accelerate>=1.4 and datasets>=4.7 floor. Keep 1:1 with the pyproject + # [project].dependencies minimums. pip install \ - "torch==2.3.0" \ - "trl==0.12.0" \ - "peft==0.11.0" \ + "torch==2.4.0" \ + "transformers==5.3.0" \ + "huggingface_hub==1.3.0" \ + "peft==0.19.0" \ + "accelerate==1.4.0" \ + "datasets==4.7.0" \ + "trl==1.0.0" \ "pydantic==2.0.0" \ "pyyaml==6.0.1" \ "tensorboard==2.15.0" \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a5c90b2..8adaeca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,44 @@ All notable changes to ForgeLM are documented here. ## [Unreleased] -_(v0.8.1 dev cycle — entries will land here as PRs merge.)_ +_(v0.9.0 dev cycle — a breaking dependency migration; entries land here as PRs merge.)_ + +### Changed + +- **transformers 5.x migration (breaking).** Raised the core dependency floor to + `transformers>=5.3.0,<6.0.0` and cascaded the co-dependencies it requires: + `torch>=2.4.0`, `huggingface_hub>=1.3.0,<2.0.0`, `peft>=0.19.0`, + `accelerate>=1.4.0`, `datasets>=4.7.0,<6.0.0`, `trl>=1.0.0`, and + `requests>=2.32.2` (trl 1.0 pulls the `accelerate>=1.4` / `datasets>=4.7` + floor; datasets 4.7 pulls `requests>=2.32.2`). transformers 5.3.0 is the first + release carrying the fix for **CVE-2026-4372**; the previous `<5.0.0` pin could + not reach it. The `test-min-deps` nightly floor and `tools/pip_audit_ignores.yaml` + were updated to match the new minimums. +- **`from_pretrained` dtype kwarg.** Renamed `torch_dtype=` → `dtype=` at the two + base-model load sites (`export.py`, `synthetic.py`); `torch_dtype` is a + deprecated alias in transformers 5 slated for removal. Behaviour is unchanged. + +### Removed + +- **transformers 4.x support (breaking).** ForgeLM now requires transformers 5.x. + The `safe_serialization=True` kwarg (removed from `save_pretrained` in + transformers 5, where safetensors is the enforced default) was dropped from the + three model-save call sites (`export.py`, `merging.py`, `trainer.py`) — the + on-disk output is unchanged. +- **Intel Mac (x86_64) support (breaking).** transformers 5 requires `torch>=2.4`, + for which PyPI publishes no x86_64-Darwin wheel, so that platform can no longer + install ForgeLM's core stack. Apple Silicon, Linux, and Windows are unaffected. + The now-moot `numpy<2; darwin x86_64` ABI-guard marker was removed with it. + +### Security + +- **CVE-2026-4372** — a critical `AutoModelForCausalLM.from_pretrained()` RCE in + transformers <5.3.0 (a malicious `config.json` `_attn_implementation_internal` + field downloads and executes attacker code, bypassing `trust_remote_code`) — is + resolved by the `transformers>=5.3.0` floor above. Both transformers + suppressions in `tools/pip_audit_ignores.yaml` — `CVE-2026-1839` (fixed in + 5.0.0rc3) and `PYSEC-2025-217` / `CVE-2025-14929` (X-CLIP RCE, OSV + last-affected 5.0.0rc0) — are now inert under the raised pin and were removed. ## [0.8.0] — 2026-06-16 diff --git a/README.md b/README.md index 10f69e7f..a211030f 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ cd ForgeLM pip install -e . ``` -**Prerequisites:** Python 3.10+, `torch>=2.2.0`. Platform-specific notes are in the [installation guide](docs/usermanuals/en/getting-started/installation.md). +**Prerequisites:** Python 3.10+, `torch>=2.4.0` (required by `transformers>=5.3.0`). Intel Macs (x86_64) are no longer supported — PyPI has no `torch>=2.4` wheel for that platform. Platform-specific notes are in the [installation guide](docs/usermanuals/en/getting-started/installation.md). ### Optional extras diff --git a/docs/reference/supply_chain_security-tr.md b/docs/reference/supply_chain_security-tr.md index 20685599..20adc504 100644 --- a/docs/reference/supply_chain_security-tr.md +++ b/docs/reference/supply_chain_security-tr.md @@ -133,9 +133,10 @@ annotation olarak loglanır; audit trail görünür kalır. ForgeLM'in kendi nightly'si proje-içi triage için check-in edilmiş bir [`tools/pip_audit_ignores.yaml`](../../tools/pip_audit_ignores.yaml) -taşır (şu an **iki** transformers advisory'si (`CVE-2026-1839` ve -`PYSEC-2025-217` / `CVE-2025-14929`), dokuz no-fix torch advisory'si ve -bir OSV-yanlış-sınıflandırılmış markdown kaydı). Bu dosya yalnız projenin kendi workflow'u tarafından +taşır (şu an dokuz no-fix torch advisory'si ve bir +OSV-yanlış-sınıflandırılmış markdown kaydı; daha önce taşıdığı iki +transformers advisory'si `transformers>=5.3.0` floor'u onları pip-audit +tarafından raporlanamaz hâle getirince kaldırıldı). Bu dosya yalnız projenin kendi workflow'u tarafından `--ignores` ile tüketilir; deployer'lar miras almaz. Her release cycle'da gözden geçirilir. diff --git a/docs/reference/supply_chain_security.md b/docs/reference/supply_chain_security.md index 398e3f8a..17500bfb 100644 --- a/docs/reference/supply_chain_security.md +++ b/docs/reference/supply_chain_security.md @@ -133,9 +133,10 @@ the run summary so the audit trail stays visible. ForgeLM's own nightly does carry a checked-in [`tools/pip_audit_ignores.yaml`](../../tools/pip_audit_ignores.yaml) for -project-internal triage (currently **two** transformers advisories -(`CVE-2026-1839` and `PYSEC-2025-217` / `CVE-2025-14929`), nine -no-fix torch advisories, and one OSV-misclassified markdown record). That file is consumed only by the +project-internal triage (currently nine no-fix torch advisories and one +OSV-misclassified markdown record; the two transformers advisories it once +carried were dropped once the `transformers>=5.3.0` floor made them +unreportable by pip-audit). That file is consumed only by the project's own workflow via `--ignores`; deployers do not inherit it. Review at every release cycle. diff --git a/docs/usermanuals/en/getting-started/installation.md b/docs/usermanuals/en/getting-started/installation.md index b94b1347..5a8f7614 100644 --- a/docs/usermanuals/en/getting-started/installation.md +++ b/docs/usermanuals/en/getting-started/installation.md @@ -21,6 +21,10 @@ ForgeLM ships as a single PyPI package with optional dependency groups (called * **No GPU?** You can still use ForgeLM for ingestion, audit, evaluation prep, and deployment config generation — every CPU-only workflow runs on the same `forgelm` command. ::: +:::note +**Intel Macs (x86_64) are not supported.** ForgeLM requires `torch>=2.4.0` (pulled in by `transformers>=5.3.0`), and PyPI publishes no `torch>=2.4` wheel for x86_64 macOS. Use Apple Silicon, Linux, or Windows. +::: + ## Base install ```shell diff --git a/docs/usermanuals/tr/getting-started/installation.md b/docs/usermanuals/tr/getting-started/installation.md index e01b7ec1..2864ee14 100644 --- a/docs/usermanuals/tr/getting-started/installation.md +++ b/docs/usermanuals/tr/getting-started/installation.md @@ -21,6 +21,10 @@ ForgeLM, PyPI'da tek bir paket olarak gelir; ağır özellikler için opsiyonel **GPU yok mu?** ForgeLM'i ingest, audit, eval hazırlığı ve deployment config üretimi için yine de kullanabilirsiniz — her CPU-only iş akışı aynı `forgelm` komutuyla çalışır. ::: +:::note +**Intel Mac'ler (x86_64) desteklenmiyor.** ForgeLM `torch>=2.4.0` gerektirir (`transformers>=5.3.0` üzerinden gelir) ve PyPI'da x86_64 macOS için `torch>=2.4` wheel'i yok. Apple Silicon, Linux veya Windows kullanın. +::: + ## Temel kurulum ```shell diff --git a/forgelm/export.py b/forgelm/export.py index a45b0ce9..e032b789 100644 --- a/forgelm/export.py +++ b/forgelm/export.py @@ -143,9 +143,10 @@ def _find_converter_script() -> str: def _merge_adapter(model_path: str, adapter_path: str, merged_dir: str) -> None: """Merge a PEFT LoRA adapter into the base model and save to *merged_dir*. - Forces ``torch_dtype=float16`` for the merge: GGUF f16 conversion will cast + Forces ``dtype=float16`` for the merge: GGUF f16 conversion will cast to fp16 anyway, and bf16 math on CPU silently falls back to fp32 (much - slower for large models). + slower for large models). (``dtype`` is the transformers-5 name for the + former ``torch_dtype`` kwarg.) """ import torch from peft import PeftModel @@ -158,13 +159,16 @@ def _merge_adapter(model_path: str, adapter_path: str, merged_dir: str) -> None: # architecture should fork and pre-convert before exporting. tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=False) model = AutoModelForCausalLM.from_pretrained( - model_path, torch_dtype=torch.float16, device_map="cpu", trust_remote_code=False + model_path, dtype=torch.float16, device_map="cpu", trust_remote_code=False ) model = PeftModel.from_pretrained(model, adapter_path) model = model.merge_and_unload() os.makedirs(merged_dir, exist_ok=True) - model.save_pretrained(merged_dir, safe_serialization=True) + # safetensors is the enforced default in transformers 5.x, which removed the + # `safe_serialization` kwarg from save_pretrained entirely — passing it now + # raises TypeError. Dropping it preserves the previous safetensors output. + model.save_pretrained(merged_dir) tokenizer.save_pretrained(merged_dir) logger.info("Merged model saved to %s", merged_dir) diff --git a/forgelm/merging.py b/forgelm/merging.py index 848b8f48..d45dedbe 100644 --- a/forgelm/merging.py +++ b/forgelm/merging.py @@ -91,9 +91,11 @@ def merge_peft_adapters( else: return MergeResult(success=False, error=f"Unknown merge method: {method}") - # Save merged model + # Save merged model. transformers 5.x removed the `safe_serialization` + # kwarg (safetensors is now the enforced default); passing it raises + # TypeError, so we rely on the default. os.makedirs(output_dir, exist_ok=True) - merged.save_pretrained(output_dir, safe_serialization=True) + merged.save_pretrained(output_dir) tokenizer.save_pretrained(output_dir) logger.info("Merged model saved to %s", output_dir) diff --git a/forgelm/synthetic.py b/forgelm/synthetic.py index e3fd4861..29162f8f 100644 --- a/forgelm/synthetic.py +++ b/forgelm/synthetic.py @@ -278,7 +278,8 @@ def _load_local_teacher(self): tokenizer = AutoTokenizer.from_pretrained(self.synth_cfg.teacher_model, trust_remote_code=False) model = AutoModelForCausalLM.from_pretrained( self.synth_cfg.teacher_model, - torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, + # ``dtype`` is the transformers-5 name for the former ``torch_dtype``. + dtype=torch.float16 if torch.cuda.is_available() else torch.float32, device_map="auto" if torch.cuda.is_available() else None, trust_remote_code=False, ) diff --git a/forgelm/trainer.py b/forgelm/trainer.py index f23c61a4..7ead1f10 100644 --- a/forgelm/trainer.py +++ b/forgelm/trainer.py @@ -615,9 +615,11 @@ def _get_training_args_for_type(self): kwargs["packing"] = bool(getattr(self.config.training, "packing", False)) kwargs["dataset_text_field"] = "text" # Sequence-length cap parameter was renamed in trl 0.13: - # trl 0.12.x: ``SFTConfig(max_seq_length=...)`` - # trl 0.13+ / 1.x: ``SFTConfig(max_length=...)`` — old name removed - # ``pyproject.toml`` pins ``trl>=0.12.0,<2.0.0``, so detect at runtime. + # trl 0.13+ / 1.x: ``SFTConfig(max_length=...)`` — the current floor + # trl 0.12.x: ``SFTConfig(max_seq_length=...)`` — pre-floor fallback + # ``pyproject.toml`` now pins ``trl>=1.0.0,<2.0.0``, so ``max_length`` + # is always present; we still detect at runtime so a future rename + # fails loud rather than silently dropping the cap. # Hard-fail (don't warn-and-continue) if neither name is exposed: # silently dropping ``model.max_length`` would let TRL pick its # default and produce a model trained against an unintended @@ -1525,7 +1527,9 @@ def save_final_model(self, final_path: str) -> None: model_to_save = self.trainer.model try: merged = model_to_save.merge_and_unload() - merged.save_pretrained(final_path, safe_serialization=True) + # transformers 5.x removed the `safe_serialization` kwarg + # (safetensors is the enforced default); passing it raises TypeError. + merged.save_pretrained(final_path) except (OSError, RuntimeError, AttributeError, ValueError) as e: # AttributeError: non-PEFT model lacking merge_and_unload. # RuntimeError: torch-side merge / dtype / device errors. diff --git a/pyproject.toml b/pyproject.toml index b4d2a0a3..4350a798 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta" [project] name = "forgelm" -version = "0.8.1rc1" +version = "0.9.0rc1" description = "Config-driven LLM fine-tuning with safety evaluation, EU AI Act compliance, 6 alignment methods, and one-command bundled quickstart templates." readme = "README.md" requires-python = ">=3.10" @@ -39,25 +39,23 @@ classifiers = [ ] dependencies = [ - "torch>=2.2.0,<3.0.0", - # Intel Mac (x86_64) is wheel-locked to torch 2.2.x — PyTorch Foundation - # stopped publishing >=2.3 wheels for x86_64 Darwin. torch 2.2 was - # compiled against NumPy 1.x; without this marker pip pulls numpy>=2 and - # triggers an ABI mismatch (`_ARRAY_API not found`) that silently - # degrades the numpy bridge inside torch. Other platforms keep numpy - # unconstrained — torch 2.3+ on Linux / Apple Silicon / Windows is - # binary-compatible with NumPy 2.x. - "numpy<2; sys_platform == 'darwin' and platform_machine == 'x86_64'", - "transformers>=4.38.0,<5.0.0", - "peft>=0.9.0,<1.0.0", - "datasets>=2.21.0,<5.0.0", - "accelerate>=0.27.0,<2.0.0", + "torch>=2.4.0,<3.0.0", + # transformers 5.x requires torch>=2.4. PyTorch Foundation never published + # >=2.3 wheels for x86_64 Darwin, so Intel Macs (x86_64) can no longer + # install ForgeLM's core dependency stack — that platform was dropped with + # the transformers-5 migration (v0.9.0). Apple Silicon, Linux and Windows + # are unaffected. The previous `numpy<2; darwin x86_64` ABI-guard marker is + # gone with the platform it protected (torch 2.4+ ships against NumPy 2.x). + "transformers>=5.3.0,<6.0.0", + "peft>=0.19.0,<1.0.0", + "datasets>=4.7.0,<6.0.0", + "accelerate>=1.4.0,<2.0.0", "pyyaml>=6.0.1,<7.0.0", "pydantic>=2.0.0,<3.0.0", - "trl>=0.12.0,<2.0.0", + "trl>=1.0.0,<2.0.0", "tensorboard>=2.15.0,<3.0.0", - "huggingface_hub>=0.23.2,<1.0.0", - "requests>=2.31.0,<3.0.0", + "huggingface_hub>=1.3.0,<2.0.0", + "requests>=2.32.2,<3.0.0", # requests-toolbelt is required by forgelm._http for the HostHeaderSSLAdapter # used to close the DNS-rebinding TOCTOU window in safe_post / safe_get # (issue #14). Small (~30KB) and from the same requests ecosystem, so it's diff --git a/tools/pip_audit_ignores.yaml b/tools/pip_audit_ignores.yaml index 7fc9bb63..cd16a831 100644 --- a/tools/pip_audit_ignores.yaml +++ b/tools/pip_audit_ignores.yaml @@ -33,48 +33,13 @@ ignores: # ----- transformers ----- - - - id: CVE-2026-1839 - package: transformers - reason: >- - Fix only in transformers 5.0.0rc3 (release candidate); pyproject - pins <5.0.0 due to TRL adapter + tokenizer-config breaking - changes. - threat_model: >- - ForgeLM's pyproject pins ``transformers>=4.38.0,<5.0.0``. The - 5.x branch is a major version bump that breaks downstream - callers (TRL adapter signature changes + tokenizer-config API - drift). No 4.x backport at time of ignore. - verified_at: '2026-05-10' - reevaluate_after: >- - Each release cycle, or when transformers ships a 4.x point - release with the fix, or when ForgeLM cuts a tracked - major-version-bump cycle that raises the upper bound. - - - id: PYSEC-2025-217 - aliases: [CVE-2025-14929] - package: transformers - reason: >- - CVE-2025-14929 — X-CLIP checkpoint-conversion deserialization - RCE (CVSS:3.0 AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H). The - advisory records no fixed version for the 4.x line that - pyproject pins. - threat_model: >- - CVSS is local + user-interaction (AV:L/UI:R): the target must - open a malicious checkpoint/file. Same trust model as - ``torch.load(pickle)`` — operators are documented as required - to only load trusted checkpoints. Codebase check on - 2026-05-24 (``git grep`` over ``forgelm/``): no X-CLIP usage, - no checkpoint-conversion path other than the HF->GGUF export - wrapper, and zero direct ``torch.load`` call sites — all model - loading goes through HuggingFace ``from_pretrained``. The - vulnerable X-CLIP conversion path is therefore not reachable - from ForgeLM code. - verified_at: '2026-05-24' - reevaluate_after: >- - Each release cycle, or when the advisory gains a fixed version - within the pinned ``transformers<5.0.0`` range, or when ForgeLM - bumps to a transformers release that carries the fix. + # + # No transformers advisories are currently suppressed. Two were dropped in + # the v0.9.0 transformers-5 migration once pyproject pinned + # transformers>=5.3.0 — pip-audit no longer reports either under that floor, + # so a suppression would be dead weight: + # - CVE-2026-1839 — fixed in transformers 5.0.0rc3. + # - PYSEC-2025-217 / CVE-2025-14929 (X-CLIP RCE) — OSV last_affected 5.0.0rc0. # ----- torch (added 2026-05-21, triage in issue #58) ----- # From 9974886cd7d0e110d6f701c30d9ba94acd95ccd2 Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Sun, 5 Jul 2026 00:41:07 +0300 Subject: [PATCH 2/3] test(pip-audit): update ignore-file smoke test after removing transformers suppressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v0.9.0 transformers-5 migration removed both transformers advisories (CVE-2026-1839, PYSEC-2025-217) from tools/pip_audit_ignores.yaml — pip-audit no longer reports either under the transformers>=5.3.0 pin. The structural loader/indexing smoke test asserted CVE-2026-1839 was still present; point it at a still-present torch entry (PYSEC-2025-191) instead. Fixes the sole test failure in PR #68 (2845 passed, 1 failed on tf5). Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/test_check_pip_audit.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_check_pip_audit.py b/tests/test_check_pip_audit.py index 5358bbd9..212f771e 100644 --- a/tests/test_check_pip_audit.py +++ b/tests/test_check_pip_audit.py @@ -584,7 +584,11 @@ def test_project_ignore_file_passes_schema_validation(tool): assert project_file.exists(), "project ignore file must be checked in" loaded = tool._load_ignores(project_file) assert loaded is not None, "checked-in ignore file failed schema validation" - # Every entry indexed under at least its primary id. - assert "CVE-2026-1839" in loaded, ( - "the historical transformers ignore must still be present; if you removed it intentionally update this test" + # Every entry indexed under at least its primary id. (The transformers + # advisories that used to be asserted here — CVE-2026-1839 and + # PYSEC-2025-217 — were dropped in the v0.9.0 transformers-5 migration: + # pip-audit no longer reports either under the transformers>=5.3.0 pin. + # Assert on a still-present torch entry so the loader/indexing check holds.) + assert "PYSEC-2025-191" in loaded, ( + "a known suppression must be indexed by its primary id; if you removed it intentionally update this test" ) From f9c7651d4f27472c9e6f3daf77b979c6e3e61f0c Mon Sep 17 00:00:00 2001 From: Cemil ILIK Date: Sun, 5 Jul 2026 00:45:58 +0300 Subject: [PATCH 3/3] refactor(trainer): drop dead max_seq_length branch now that trl>=1.0.0 Per PR #68 review: with the trl>=1.0.0,<2.0.0 floor from the transformers-5 migration, SFTConfig always exposes `max_length` (the `max_seq_length` name was retired after trl 0.12.x), so the fallback branch was unreachable dead code. Drive `max_length` directly, but KEEP the runtime signature check + hard-fail so a future trl that removes/hides the parameter still fails loud rather than silently dropping the configured sequence-length cap (docs/standards/error-handling.md "no silent failures"). The hard-fail error message still names both parameters, so the two future-proofing regression tests are unaffected. Removes the now-obsolete legacy-trl-0.12.x test (that path is below the supported floor). Co-Authored-By: Claude Opus 4.8 (1M context) --- forgelm/trainer.py | 36 +++++++++++++------------------- tests/test_trainer_sft_config.py | 30 +++----------------------- 2 files changed, 18 insertions(+), 48 deletions(-) diff --git a/forgelm/trainer.py b/forgelm/trainer.py index 7ead1f10..0eb2c341 100644 --- a/forgelm/trainer.py +++ b/forgelm/trainer.py @@ -614,37 +614,31 @@ def _get_training_args_for_type(self): kwargs["packing"] = bool(getattr(self.config.training, "packing", False)) kwargs["dataset_text_field"] = "text" - # Sequence-length cap parameter was renamed in trl 0.13: - # trl 0.13+ / 1.x: ``SFTConfig(max_length=...)`` — the current floor - # trl 0.12.x: ``SFTConfig(max_seq_length=...)`` — pre-floor fallback - # ``pyproject.toml`` now pins ``trl>=1.0.0,<2.0.0``, so ``max_length`` - # is always present; we still detect at runtime so a future rename - # fails loud rather than silently dropping the cap. - # Hard-fail (don't warn-and-continue) if neither name is exposed: - # silently dropping ``model.max_length`` would let TRL pick its - # default and produce a model trained against an unintended - # context window — exactly the "no silent failures" rule in + # ``SFTConfig``'s sequence-length cap is ``max_length`` in trl 0.13+ + # (the ``max_seq_length`` name was retired after trl 0.12.x). + # ``pyproject.toml`` pins ``trl>=1.0.0,<2.0.0``, so every supported + # trl exposes ``max_length``; we drive it directly but still verify + # it via the runtime signature and hard-fail if a future trl removes + # or hides the parameter. Silently dropping ``model.max_length`` + # would let TRL pick its default and train against an unintended + # context window — the "no silent failures" rule in # docs/standards/error-handling.md. sft_params = inspect.signature(SFTConfig).parameters - if "max_length" in sft_params: - kwargs["max_length"] = self.config.model.max_length - elif "max_seq_length" in sft_params: - kwargs["max_seq_length"] = self.config.model.max_length - else: + if "max_length" not in sft_params: # Probe the trl version off the already-loaded module rather - # than re-importing — ``from trl import SFTConfig`` above - # left trl in ``sys.modules`` and ``SFTConfig.__module__`` - # gives us the canonical package name without a redundant - # ``import trl`` line. + # than re-importing — ``from trl import SFTConfig`` above left + # trl in ``sys.modules`` and ``SFTConfig.__module__`` gives the + # canonical package name without a redundant ``import trl``. trl_module = sys.modules.get(SFTConfig.__module__.split(".", 1)[0]) trl_version = getattr(trl_module, "__version__", "?") raise ValueError( f"SFTConfig in trl {trl_version} exposes neither " - "`max_length` nor `max_seq_length` as a named parameter; cannot apply " - f"the sequence-length cap from config (model.max_length={self.config.model.max_length}). " + "`max_length` nor the legacy `max_seq_length` as a named parameter; " + f"cannot apply the sequence-length cap from config (model.max_length={self.config.model.max_length}). " f"Detected parameters: {sorted(sft_params)}. Pin trl to a known-compatible " "version or file an issue referencing this error." ) + kwargs["max_length"] = self.config.model.max_length return SFTConfig(**kwargs) elif tt == "orpo": diff --git a/tests/test_trainer_sft_config.py b/tests/test_trainer_sft_config.py index 8f9d62a9..92a49b42 100644 --- a/tests/test_trainer_sft_config.py +++ b/tests/test_trainer_sft_config.py @@ -5,8 +5,9 @@ name. Passing the old kwarg raises ``TypeError`` at trainer args build time and aborts training on every modern trl install. -``pyproject.toml`` pins ``trl>=0.12.0,<2.0.0``, so the trainer must -detect at runtime which parameter the installed ``SFTConfig`` accepts. +``pyproject.toml`` pins ``trl>=1.0.0,<2.0.0`` (all expose ``max_length``), +so the trainer drives ``max_length`` directly but verifies it at runtime +and hard-fails if a future trl removes or hides the parameter. """ from __future__ import annotations @@ -77,31 +78,6 @@ def __init__(self, *, max_length=None, packing=False, dataset_text_field=None, * assert captured_kwargs["dataset_text_field"] == "text" -def test_sft_config_uses_max_seq_length_on_legacy_trl(tmp_path): - """trl 0.12.x: ``SFTConfig`` accepts ``max_seq_length`` only. The - trainer must fall back to that name when the modern ``max_length`` - parameter is unavailable.""" - - captured_kwargs: dict = {} - - class FakeLegacySFTConfig: - def __init__(self, *, max_seq_length=None, packing=False, dataset_text_field=None, **other): - captured_kwargs["max_seq_length"] = max_seq_length - captured_kwargs["packing"] = packing - captured_kwargs["dataset_text_field"] = dataset_text_field - captured_kwargs.update(other) - - trainer = _seed_trainer(tmp_path) - - with patch("trl.SFTConfig", FakeLegacySFTConfig): - trainer._get_training_args_for_type() - - assert captured_kwargs["max_seq_length"] == 2048 - assert "max_length" not in captured_kwargs, ( - "Modern `max_length` kwarg must NOT be passed to legacy trl 0.12.x SFTConfig." - ) - - def test_sft_config_passes_packing_and_dataset_text_field(tmp_path): """Regression: ``packing`` and ``dataset_text_field`` must keep being propagated alongside the sequence-length cap."""