# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. # # Cross-version compat canary for the four upstream packages whose # release cadence regularly breaks unsloth + unsloth-zoo: # # 1. vLLM (LoRA worker manager, BnB loader, cumem allocator) # 2. TRL / GRPO (trainer source rewriters in unsloth.models.rl*) # 3. PEFT (LoraConfig, get_peft_model, LoraLayer, bnb integration) # 4. sentence-transformers (Transformer/Pooling/Normalize, Trainer) # 5. bitsandbytes (Linear4bit, dequantize_4bit) # # Strategy: GitHub raw-fetch + symbol grep against every tracked # version (no pip install, CPU-only). When upstream renames a symbol # we depend on, the matching test fails BEFORE a user hits it. The # `main` branch entries give us a few-day lead on PyPI releases. # # Cross-references: # tests/vllm_compat/test_vllm_pinned_symbols.py (vLLM symbols) # tests/version_compat/test_trl_grpo_pinned_symbols.py # tests/version_compat/test_peft_pinned_symbols.py # tests/version_compat/test_sentence_transformers_pinned_symbols.py # tests/version_compat/test_bitsandbytes_pinned_symbols.py name: Version Compat CI on: pull_request: # Trigger on any unsloth source change, not just the three previously # named files. The symbol-existence tests verify that EVERY pinned # upstream reference in unsloth still resolves; a new # `from peft.foo import Bar` added in unsloth/kernels/whatever.py # is just as much a compat regression risk as one added in # unsloth/models/rl.py. paths: - 'unsloth/**' - 'tests/vllm_compat/**' - 'tests/version_compat/**' - 'pyproject.toml' - '.github/workflows/version-compat-ci.yml' schedule: # Daily 06:43 UTC. Catches upstream PyPI releases roughly within # 24 h. Off the :00 / :30 fleet-collision spots. - cron: '43 6 * * *' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: vllm-pinned-symbols: name: vLLM pinned-symbol matrix (≥ 0.9.0 + main) runs-on: ubuntu-latest timeout-minutes: 12 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' cache: 'pip' - name: Install pytest only # The test fetches from raw.githubusercontent.com and greps # source. No pip install of vllm / torch / transformers is # needed — that's the whole point of this canary. run: | python -m pip install --upgrade pip pip install 'pytest>=8' - name: Run vllm-compat suite env: # Authenticated requests get a 5000-req/h quota on raw # fetches; unauthenticated is 60/h and trips on the matrix. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | python -m pytest tests/vllm_compat/test_vllm_pinned_symbols.py -v --tb=short trl-grpo-pinned-symbols: name: TRL / GRPO pinned-symbol matrix runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' cache: 'pip' - name: Install pytest only run: | python -m pip install --upgrade pip pip install 'pytest>=8' - name: Run trl-compat suite env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # PYTHONPATH=. so `from tests.version_compat._fetch import …` # works without an editable install of unsloth itself. PYTHONPATH=. python -m pytest \ tests/version_compat/test_trl_grpo_pinned_symbols.py \ -v --tb=short peft-pinned-symbols: name: PEFT pinned-symbol matrix (pyproject window + main) runs-on: ubuntu-latest timeout-minutes: 8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' cache: 'pip' - name: Install pytest only run: | python -m pip install --upgrade pip pip install 'pytest>=8' - name: Run peft-compat suite env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PYTHONPATH=. python -m pytest \ tests/version_compat/test_peft_pinned_symbols.py \ tests/version_compat/test_unsloth_zoo_save_merged_pinned_symbols.py \ -v --tb=short st-pinned-symbols: name: sentence-transformers pinned-symbol matrix runs-on: ubuntu-latest timeout-minutes: 8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' cache: 'pip' - name: Install pytest only run: | python -m pip install --upgrade pip pip install 'pytest>=8' - name: Run sentence-transformers compat suite env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PYTHONPATH=. python -m pytest \ tests/version_compat/test_sentence_transformers_pinned_symbols.py \ -v --tb=short bitsandbytes-pinned-symbols: name: bitsandbytes pinned-symbol matrix runs-on: ubuntu-latest timeout-minutes: 8 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' cache: 'pip' - name: Install pytest only run: | python -m pip install --upgrade pip pip install 'pytest>=8' - name: Run bitsandbytes compat suite env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PYTHONPATH=. python -m pytest \ tests/version_compat/test_bitsandbytes_pinned_symbols.py \ -v --tb=short transformers-pinned-symbols: name: transformers pinned-symbol matrix (4.57.6 + 5.x + main) runs-on: ubuntu-latest timeout-minutes: 12 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' cache: 'pip' - name: Install pytest only run: | python -m pip install --upgrade pip pip install 'pytest>=8' - name: Run transformers compat suite env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PYTHONPATH=. python -m pytest \ tests/version_compat/test_transformers_pinned_symbols.py \ -v --tb=short # Optional second layer: actually `pip install` ONE representative # version of each package and verify unsloth + unsloth-zoo modules # import on it under the existing CUDA spoof. CPU-only, runs on # ubuntu-latest. Catches the small set of breakages that the static # symbol check misses (e.g. import-time side effects). zoo-imports-under-spoof: name: unsloth_zoo vllm/grpo/peft/st modules import under CUDA spoof runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false path: unsloth - name: Clone unsloth-zoo @ main run: | # github.com occasionally 500s on the git fetch; retry so a # single upstream blip does not fail CI. for attempt in 1 2 3; do rm -rf "$RUNNER_TEMP/unsloth-zoo" if git clone --depth=1 https://github.com/unslothai/unsloth-zoo \ "$RUNNER_TEMP/unsloth-zoo"; then break fi if [ "$attempt" -eq 3 ]; then echo "::error::git clone unsloth-zoo failed after 3 attempts" exit 1 fi delay=$((5 * attempt)) echo "::warning::clone failed (attempt $attempt/3), retrying in ${delay}s..." sleep "$delay" done - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' cache: 'pip' - name: Install CPU torch + supported pkg pins run: | python -m pip install --upgrade pip # CPU torch (vllm/peft/st all depend on it). pip install --index-url https://download.pytorch.org/whl/cpu \ 'torch>=2.4,<2.11' 'torchvision<0.26' 'torchcodec<0.10' # torchcodec is a hard requirement on transformers 5.x: # transformers/audio_utils.py:55 does # `importlib.metadata.version("torchcodec")` UNCONDITIONALLY, # which raises PackageNotFoundError on a CPU runner that # otherwise has no audio path -- and that error trickles up # through every `import unsloth_zoo.` because # unsloth-zoo's vision_utils transitively pulls # transformers.processing_utils (-> audio_utils). The 0.10 # cap mirrors the torch 2.10 / torchvision 0.26 ABI window # we already pin above. # Ladder of supported floor versions per pyproject.toml. pip install \ 'transformers>=4.56,<5.6' 'trl>=0.22,<0.26' \ 'peft>=0.18.0' 'sentence-transformers>=5.0' \ 'accelerate>=1.0' 'datasets>=3.4,<5' \ 'bitsandbytes>=0.45.5' \ sentencepiece protobuf safetensors numpy 'pytest>=8' \ 'huggingface_hub>=0.34' tqdm packaging psutil triton Pillow # Editable-install both repos so the test imports the # checkouts (not whatever stale PyPI version pip resolved). pip install --no-deps -e "$RUNNER_TEMP/unsloth-zoo" pip install --no-deps -e ./unsloth - name: Run vllm_compat zoo-imports tests under spoof env: UNSLOTH_IS_PRESENT: '1' UNSLOTH_COMPILE_DISABLE: '1' PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python run: | cd unsloth # tests/vllm_compat/test_unsloth_zoo_imports.py: narrow vllm/grpo # import gates (5 tests). # tests/vllm_compat/test_extended_module_imports.py: full sweep # of unsloth_zoo + unsloth.models.* modules + RL dispatch # table population + FastModel API surface under spoof # (~30 tests). Catches transformers / peft / bnb symbol pin # drift at module-top BEFORE any runtime call. PYTHONPATH=. python -m pytest \ tests/vllm_compat/test_unsloth_zoo_imports.py \ tests/vllm_compat/test_extended_module_imports.py \ -v --tb=short # Daily-only: same suites but with --strict on importable upstream # tags. Schedule-only so PR jobs stay fast; cron tolerates a flake. daily-fresh-fetch: name: daily fresh-fetch sweep (cron only) if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' cache: 'pip' - name: Install pytest run: pip install 'pytest>=8' - name: Run all version-compat suites in one process (no cache) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PYTHONPATH=. python -m pytest \ tests/vllm_compat/test_vllm_pinned_symbols.py \ tests/version_compat/ \ -v --tb=short