diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d76dd6..1020535 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,11 +18,12 @@ jobs: name: Quality runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v6 + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v7 with: python-version: "3.11" enable-cache: true + cache-suffix: quality - run: uv sync --extra dev - run: uv run ruff check src tests examples - run: uv run mypy src @@ -35,11 +36,12 @@ jobs: matrix: python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v6 + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.python-version }} enable-cache: true + cache-suffix: core-${{ matrix.python-version }} - run: uv sync --extra dev - run: uv run pytest -q - run: uv run python -m compileall -q src tests examples @@ -48,11 +50,12 @@ jobs: name: Tiny reference lifecycle / CPU runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v6 + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v7 with: python-version: "3.11" enable-cache: true + cache-suffix: reference-cpu - run: uv venv --python 3.11 - name: Install CPU-only PyTorch run: uv pip install torch --index-url https://download.pytorch.org/whl/cpu @@ -66,14 +69,15 @@ jobs: name: Build distributions runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: astral-sh/setup-uv@v6 + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v7 with: python-version: "3.11" enable-cache: true + cache-suffix: build - run: uv build - run: uvx twine check dist/* - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: distributions path: dist/* diff --git a/README.md b/README.md index 8c71f3e..7105e33 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,13 @@ pytest -q The reference result explicitly labels itself a mathematical/orchestration smoke test. It is not evidence for quality, distributed correctness, throughput, mixed precision, or CUDA-kernel compatibility. +Plans for every backend can also be generated and inspected without installing an NVIDIA package: + +```bash +llm-lifecycle demo --backend automodel --output runs/automodel-plan +llm-lifecycle demo --backend megatron_bridge --output runs/bridge-plan +``` + ## NeMo AutoModel ```python diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..0c023dc --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from llm_lifecycle.cli import main + + +@pytest.mark.parametrize( + ("backend", "expected_executors"), + [ + ("reference", ["reference"]), + ("automodel", ["nemo-automodel", "nemo-automodel", "nemo-rl"]), + ("megatron_bridge", ["megatron-bridge", "megatron-bridge", "nemo-rl"]), + ], +) +def test_demo_materializes_each_backend_without_optional_packages( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + backend: str, + expected_executors: list[str], +) -> None: + output = tmp_path / backend + + assert main(["demo", "--backend", backend, "--output", str(output)]) == 0 + + document = json.loads((output / "manifest.json").read_text(encoding="utf-8")) + assert document["manifest"]["model"]["backend"] == backend + assert [stage["executor"] for stage in document["stages"]] == expected_executors + assert len(list(output.glob("stage-*-*.*"))) == len(expected_executors) + assert capsys.readouterr().out.strip() == str(output.resolve())