[build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "verl" dynamic = ["version", "dependencies", "authors", "urls"] description = "verl: Volcano Engine Reinforcement Learning for LLM" license = {text = "Apache-2.0"} readme = {file = "README.md", content-type = "text/markdown"} requires-python = ">=3.10,<3.13" # One universal uv.lock: each backend is a PEP 621 extra; mutually exclusive ones # are in [tool.uv].conflicts, so a single `uv lock` resolves them all. At runtime, # sync one conflict-free combo into .venv (shared by every Ray worker group): # # uv sync --extra fsdp --extra vllm # or: python manage_envs.py sync fsdp vllm # # All backends use torch 2.11.0: vllm/sglang/fsdp/megatron on cu130, cpu on cpu # wheels (CI/dev). Both torch worlds coexist via [tool.uv].conflicts forks. # DEFERRED: trtllm (torch 2.10.0+cu130) — its RC sdist makes `uv lock` explode. # cuDNN/NCCL: torch's wheel pins them, so after `uv lock` sync CUDNN_VERSION / # NCCL_VERSION in docker/Dockerfile.uv.cu130 to the resolved values. [project.optional-dependencies] # ---- legacy small extras (kept verbatim from setup.py) --------------------- test = ["pytest", "pre-commit", "py-spy", "pytest-asyncio", "pytest-rerunfailures"] math = ["math-verify"] mcore = ["mbridge"] # Named extra so the git source lives in one place; verl-core pulls it in, so it # is always installed (no longer opt-in). transferqueue = ["TransferQueue @ git+https://github.com/Ascend/TransferQueue.git@main"] # Shared runtime deps, included by every backend via `verl[verl-core]`. # TransferQueue is always installed (imported at top level on core paths). verl-core = [ "verl[transferqueue]", "accelerate", "codetiming", "datasets", "dill", "hydra-core", "numpy>=2.0.0", "pandas", "peft", "pyarrow>=19.0.0", "pybind11", "pylatexenc", # Unversioned: override-dependencies below sets the version per backend. "transformers", "ray[default]>=2.41.0", "torchdata", "tensordict>=0.8.0,<=0.10.0,!=0.9.0", "wandb", "packaging>=20.0", "tensorboard", "fastapi", "uvicorn", "mathruler", "qwen_vl_utils", # qwen-vl-utils' first-choice video decoder; it falls back to # torchvision.io.read_video, which torchvision 0.26 removed, so without # torchcodec every video sample dies with AttributeError. It reached only # the sglang venv (a transitive sglang dep), leaving vllm / fsdp / megatron # / cpu broken — hence verl-core. Left unpinned the way sglang has it: it is # ABI-coupled to torch and every extra here pins the same torch 2.11.0. # Decoding needs FFmpeg's shared libs (`ffmpeg`, docker/Dockerfile.uv.cu130). "torchcodec", "cachetools", "nvtx", "pytest", "pytest-asyncio", "pytest-rerunfailures", ] # Pins flash-attn once for the fsdp / megatron parents (wheelhouse-routed below). # Don't install alone — needs the parent extra's torch / cuDNN pins. flash-attn-cu130torch211 = ["flash-attn==2.8.3"] # Pins cupy once for the fsdp / megatron parents. Two unrelated consumers, both # on the training side: `ray.util.collective`'s NCCL backend is cupy-based, so # every disaggregated weight-sync engine (nccl / nixl / delta_sharded) imports # it; and Megatron MoE all-to-all uses it directly. Without it the engine module # fails to import and the backend never reaches CheckpointEngineRegistry. cupy-cu130 = ["cupy-cuda13x==14.0.1"] # ---- inference backends ---------------------------------------------------- vllm = [ "verl[verl-core]", "torch==2.11.0", "torchvision==0.26.0", "torchaudio==2.11.0", # PyPI's 0.24.0 wheel is already a cu130 / torch-2.11 abi3 build, so it needs # no wheelhouse routing and no source build. "vllm==0.24.0", # flashinfer is a transitive dep vllm 0.24 hard-pins to ==0.6.12 (pure-python # from PyPI); the wheelhouse's 0.6.13 doesn't match, so it isn't routed. "mbridge", # vllm 0.24 needs transformers>=5.5.3; 5.9.0 is the project-wide pin # (enforced in override-dependencies below). "transformers==5.9.0", ] sglang = [ "verl[verl-core]", "torch==2.11.0", "torchvision==0.26.0", "torchaudio==2.11.0", # sglang + sglang-kernel come from PyPI; upstream's 0.5.12 wheels are cu130 / # torch-2.11 builds already (sglang-kernel links libcudart.so.13). "sglang==0.5.12", # sglang pins the kernel itself (==0.4.2.post2); kept explicit so the backend # still installs it if a future sglang loosens that pin. "sglang-kernel", # sglang hard-deps flash-attn-4 but we EXCLUDE it (it collides with the # flash-attn 2.8.3 the training extras pin — see override-dependencies); it is # imported lazily (fa4 backend only), so the default backends work. "mbridge", # sglang 0.5.12 hard-pins transformers==5.6.0; the project-wide override below # replaces that pin with 5.9.0 (5.6.0 is excluded repo-wide, see setup.py). "transformers==5.9.0", ] # DEFERRED — trtllm is a CUDA-13 backend (torch 2.10.0+cu130, conflict-gated). # tensorrt-llm 1.3.0rc14's tightly-pinned RC tree makes `uv lock` explode. To # re-enable on a stable release: uncomment, add cu130 torch routing + a # `{ extra = "trtllm" }` conflict entry (both flagged below), and re-lock. # trtllm = [ # "verl[verl-core]", # "torch==2.10.0", # route to pytorch-cu130 (CUDA 13), see [tool.uv.sources] # "tensorrt-llm==1.3.0rc14 ; sys_platform == 'linux' and platform_machine == 'x86_64'", # "mbridge", # "transformers==5.9.0", # "numpy>=2.0.0,<2.4", # "ray[default]==2.54.1", # ] # ---- training backends ----------------------------------------------------- fsdp = [ "verl[verl-core]", "verl[flash-attn-cu130torch211]", "verl[cupy-cu130]", "torch==2.11.0", "torchvision==0.26.0", "torchaudio==2.11.0", "liger-kernel>=0.8.2", "trl==0.27.0", "flash-linear-attention==0.5.2", # transformers NOT pinned here: verl-core carries it and the project-wide # override below settles the version (5.9.0). See override-dependencies. ] # apex / TE / flash-attn / megatron-bridge come prebuilt from the wheelhouse; # megatron-core is git-sourced (built at sync time). onnxscript: works around an # undeclared TE import. megatron = [ "verl[verl-core]", "verl[flash-attn-cu130torch211]", "verl[cupy-cu130]", "torch==2.11.0", "torchvision==0.26.0", "torchaudio==2.11.0", "nvidia-mathdx", "apex", "transformer-engine", "onnxscript", "trl==0.27.0", "matplotlib", "liger-kernel>=0.8.2", # Two Megatron<->HF connectors: mbridge (legacy) and megatron-bridge (NVIDIA, # imported as megatron.bridge; the migration target, installed deps-free — see # [[tool.uv.dependency-metadata]] below). 0.5.2 is the wheelhouse build of the # r0.5.0 release branch. "mbridge", "megatron-bridge==0.5.2", "megatron-core", # megatron-bridge hard-imports modelopt but is installed deps-free (below), so # megatron needs it explicitly. 0.44.0: PyPI stable of bridge r0.5.0's 0.44.x. "nvidia-modelopt==0.44.0", # transformers NOT pinned here (see fsdp note): settled via override-deps below. ] # ---- CI-only extras -------------------------------------------------------- # Layered by .github/workflows on top of a backend combo so CI deps come from # the lock (`sync ci`) instead of ad-hoc `uv pip install`. No # backend depends on these, so runtime images never carry them unless asked. # # mlflow is deliberately NOT here: it caps pandas<3 and cryptography<49, and # because `ci` shares one resolution fork with every backend those caps would # drag the whole project from pandas 3.0.3 to 2.3.3. gpu_unit_tests installs it # by hand for that reason — do not "fix" that by moving it in here. ci = [ "hf-transfer", # HF_HUB_ENABLE_HF_TRANSFER=1 in the GPU test jobs "sglang-router==0.2.2", # router coverage in reward_model_sglang ] # veomni drives the SFT engine tests. Installed deps-free (empty requires-dist # in [[tool.uv.dependency-metadata]] below): its real metadata pins # datasets<=2.21.0 and a cu129 torch, both of which fight the cu130 backends. # This mirrors the `--no-deps` install the SFT workflows did by hand. veomni-sft = ["veomni==0.1.11"] # CI / unit tests / dev sanity. No GPU runtime. cpu = [ "verl[verl-core]", "torch==2.11.0", "torchvision==0.26.0", "torchaudio==2.11.0", "transformers==5.9.0", # Plain source package (torch / numpy / packaging + one pybind11 C++ ext, no # CUDA toolchain), and it imports without TE / apex — both are optional there # with pure-torch fallbacks. It is what lets the `*_on_cpu.py` tests that # exercise verl's Megatron wiring run here instead of being skipped. The # CUDA-only half of the megatron extra (apex / TE / megatron-bridge) stays out. "megatron-core", # megatron.core.transformer.moe.paged_stash `import triton` unguarded, and # `import megatron.core` reaches it. megatron-core doesn't declare triton # because on the GPU backends torch's cu130 wheel already pulls it in; the cpu # wheel doesn't, so the cpu slice has to ask for it explicitly. "triton", "pytest", "pre-commit", "py-spy", "pytest-asyncio", "pytest-rerunfailures", ] [tool.ruff] line-length = 120 exclude = ["scripts/legacy_model_merger.py"] [tool.ruff.lint] isort = {known-first-party = ["verl"]} # c.f. https://github.com/vllm-project/vllm/blob/ce8d6b75fc0586045df75ee1568a5b5f9957251b/pyproject.toml select = [ # pycodestyle "E", # Pyflakes "F", # pyupgrade "UP", # flake8-bugbear "B", # isort "I", "G", ] ignore = [ # star imports "F405", "F403", # lambda expression assignment "E731", # Loop control variable not used within loop body "B007", # f-string format "UP032", # `.log()` statement uses f-string "G004", # X | None for type annotations "UP045", # deprecated import "UP035", ] [tool.mypy] pretty = true ignore_missing_imports = true explicit_package_bases = true follow_imports = "skip" ignore_errors = true [[tool.mypy.overrides]] module = [ "verl.trainer.config.algorithm", "verl.trainer.ppo.core_algos", "verl.trainer.ppo.reward", "verl.workers.reward_manager", "verl.workers.reward_manager.*", ] ignore_errors = false [tool.setuptools] include-package-data = true [tool.setuptools.dynamic] version = {file = "verl/version/version"} [tool.setuptools.package-dir] "" = "." [tool.setuptools.package-data] verl = [ "version/*", "trainer/config/*.yaml", "trainer/config/*/*.yaml", "experimental/*/config/*.yaml", ] # One universal uv.lock: `uv lock` resolves every extra; conflicting extras # coexist in the lock but never in one venv. See docs/start/install.rst. [tool.uv] # Linux + Python 3.12 on both CPU arches verl ships CUDA images for: x86_64 and # aarch64 (GH200 / GB200 superchips). # # Arch is a RESOLUTION dimension, NOT an extra dimension. Every backend extra # above is spelled exactly once, and `uv sync --extra megatron` installs the # right wheels on either host: uv forks the lock per marker and then matches # each wheel's own platform tag. Extra names, conflict sets, manage_envs.py # combos and every CI invocation are identical on the two arches. # # That works because everything the cu130 backends need is published for both: # torch / torchvision / torchaudio cu130 (download.pytorch.org ships # manylinux_2_28_aarch64), vllm / sglang / sglang-kernel / cupy-cuda13x / # sglang-router (PyPI), and the three native wheelhouse builds apex / # flash-attn / transformer-engine (the wheelhouse builds those on # ubuntu-24.04-arm with TORCH_CUDA_ARCH_LIST "9.0;10.0" — the only CUDA parts an # arm64 wheel can land on). megatron-bridge is py3-none-any; megatron-core / # mbridge build from git. The second entry costs one extra package (sglang's # aarch64-only decord2) and leaves every pinned version unchanged. # # If a package ever IS arch-specific, do NOT fork the backend into # `megatron-x86_64` / `megatron-aarch64` extras — that doubles the conflict # matrix and every call site. Split it INSIDE the extra with a marker, either on # the requirement itself: # # "some-x86-only-pkg ; platform_machine == 'x86_64'", # # or, for more than one package, as a marker-gated sub-extra (the same # convention flash-attn-cu130torch211 / cupy-cu130 use to share a pin): # # megatron-x86_64 = ["pkg-a", "pkg-b"] # in the extras # "verl[megatron-x86_64] ; platform_machine == 'x86_64'", # inside megatron # # Either way `--extra megatron` stays the one thing anybody types. environments = [ "python_full_version >= '3.12' and sys_platform == 'linux' and platform_machine == 'x86_64'", "python_full_version >= '3.12' and sys_platform == 'linux' and platform_machine == 'aarch64'", ] # Mutually exclusive extras — at most one per set per `uv sync`. Distinct torch # worlds never mix (cu130: vllm/sglang/fsdp/megatron; cpu). vllm/sglang/cpu are # mutually exclusive; cpu also excludes fsdp/megatron. fsdp/megatron CAN combine # with a cu130 inference engine (e.g. --extra megatron --extra vllm). conflicts = [ # (trtllm DEFERRED — add `{ extra = "trtllm" }` to each set when it rejoins.) [{ extra = "vllm" }, { extra = "sglang" }, { extra = "cpu" }], [{ extra = "fsdp" }, { extra = "cpu" }], [{ extra = "megatron" }, { extra = "cpu" }], ] # Global override pins: # * transformers==5.9.0 project-wide — one version for every backend, so a # venv's transformers no longer depends on which engine it was synced with. # Unmarked on purpose: overrides are absolute and replace EVERY requirement # on the package, so this also settles the engines' own pins (vllm 0.24 asks # >=5.5.3, sglang 0.5.12 hard-pins ==5.6.0 — a release excluded repo-wide, # see setup.py). Keep it inside setup.py / requirements.txt's supported # window (>=5.5.3,!=5.6.0,<5.11) and bump all of them together. Do NOT add # an `extra` marker back unless a backend truly needs to diverge: such a # marker matches only a requirement verl declares under that extra, never a # transitive one, and a fork no override matches drops transformers outright. # * numpy>=2.0.0 floor. # * kernels>=0.12.0,<0.13 — sglang deps `kernels` unbounded; later releases # broke transformers' hub_kernels.py before (huggingface/transformers#46291), # so pin to transformers 5.9.0's own window (satisfies sglang too) and move # it whenever the transformers pin moves. # * flash-attn-4 (sglang's hard dep) is DROPPED — its marker is false in every # declared environment, which removes the requirement outright. It ships the # same `flash_attn/cute/` files as flash-attn 2.8.3 (the fsdp / megatron pin), # so installing both leaves whichever landed last on disk. TransformerEngine # imports flash_attn.cute.interface as soon as flash-attn-4's dist metadata # exists, so once 2.8.3's older copy wins, `import megatron.core` dies with # "No module named 'cutlass.utils.ampere_helpers'" — a module the # nvidia-cutlass-dsl 4.5 that flashinfer pulls in no longer has. # docker/Dockerfile.stable.sglang settles the same clash with # `pip uninstall -y flash-attn-4`; sglang itself only imports fa4 for its # non-default "fa4" attention backend. override-dependencies = [ "transformers==5.9.0", "numpy>=2.0.0", "kernels>=0.12.0,<0.13", "flash-attn-4 ; platform_machine == 'flash-attn-4-excluded'", # aarch64 only: nvidia-cusparselt-cu13 0.8.0/0.8.1 ship an internal WHEEL tag # of `manylinux2014_sbsa` while the filename says `aarch64`. uv rejects the # mismatch and reinstalls the package on EVERY `uv run`; 100+ concurrent Ray # workers then race deleting libcusparseLt.so.0 and crash torch import # (astral-sh/uv#17711). 0.9.1 fixed the internal tag to `manylinux2014_aarch64` # and moves the lib to nvidia/cu13/lib/, which torch 2.11's _preload_cuda_deps # already searches ("nvidia/cuXX/lib since CUDA 13.0"). SONAME stays .so.0. # x86_64 keeps 0.8.0 (its tag is correct; no reinstall) to avoid churning a # working config. "nvidia-cusparselt-cu13==0.8.0 ; platform_machine == 'x86_64' and sys_platform == 'linux'", "nvidia-cusparselt-cu13==0.9.1 ; platform_machine == 'aarch64' and sys_platform == 'linux'", ] # Skip lock-time metadata builds for git-source packages with no [project] / # dynamic version (the sync-time source build still runs). Versions track # [tool.uv.sources] below; bump together. (Wheelhouse packages read metadata from # the wheel — no entry needed, except megatron-bridge, whose wheel metadata we # deliberately discard; see below.) [[tool.uv.dependency-metadata]] name = "megatron-core" version = "0.18.0" requires-dist = ["torch>=2.6.0", "numpy", "packaging>=24.2"] [[tool.uv.dependency-metadata]] name = "mbridge" version = "0.15.1" requires-dist = [] # megatron-bridge 0.5.2 comes prebuilt from the wheelhouse (megatron-bridge-r0.5.0 # tag), pairing with megatron-core 0.18.0. The wheel's real metadata pulls a # conflicting transformers + NeMo stack (plus its own flashinfer pins), so we # declare EMPTY requires-dist (like `pip install --no-deps`) — the megatron extra # already provides torch / megatron-core / TE. [[tool.uv.dependency-metadata]] name = "megatron-bridge" version = "0.5.2" requires-dist = [] # veomni 0.1.11 (veomni-sft extra) is likewise installed deps-free: its metadata # pins datasets<=2.21.0 and a cu129 torch/torchvision/torchaudio set that cannot # coexist with the cu130 backends. The SFT tests only need veomni's own modules. [[tool.uv.dependency-metadata]] name = "veomni" version = "0.1.11" requires-dist = [] # Heavy native packages come prebuilt from the verl wheelhouse (cu130 / torch 2.11 # / cp312, built for x86_64 AND aarch64) — no source build here. megatron-bridge # rides along (pure-python wheel off the same wheelhouse, so no git clone / build # either), and being py3-none-any it is the one wheelhouse component with a single # arch-independent build. # # The inference engines are deliberately NOT routed: upstream publishes cu130 / # torch-2.11 wheels for the pinned vllm / sglang / sglang-kernel versions on PyPI, # so the wheelhouse copies bought nothing (same builds, same download size, same # install time) while costing a hash-less index entry. flashinfer stays on PyPI # for the same reason — vllm/sglang pin their own and the wheelhouse's 0.6.13 # matches neither. [tool.uv.sources] apex = { index = "verl-wheelhouse" } flash-attn = { index = "verl-wheelhouse" } transformer-engine = { index = "verl-wheelhouse" } megatron-bridge = { index = "verl-wheelhouse" } # megatron-core / mbridge aren't in the wheelhouse; git-sourced (built at sync # time). The wheelhouse megatron-bridge 0.5.2 (r0.5.0 branch) pairs with # megatron-core core_v0.18.0 — bump the two together. mbridge = { git = "https://github.com/ISEEKYAN/mbridge.git", rev = "641a5a0" } megatron-core = { git = "https://github.com/NVIDIA/Megatron-LM.git", tag = "core_v0.18.0" } # Per-extra routing to the matching pytorch wheel index. torch = [ { index = "pytorch-cu130", extra = "vllm" }, { index = "pytorch-cu130", extra = "sglang" }, { index = "pytorch-cu130", extra = "fsdp" }, { index = "pytorch-cu130", extra = "megatron" }, { index = "pytorch-cpu", extra = "cpu" }, # trtllm (DEFERRED) would route to CUDA 13 here: # { index = "pytorch-cu130", extra = "trtllm" }, ] torchvision = [ { index = "pytorch-cu130", extra = "vllm" }, { index = "pytorch-cu130", extra = "sglang" }, { index = "pytorch-cu130", extra = "fsdp" }, { index = "pytorch-cu130", extra = "megatron" }, { index = "pytorch-cpu", extra = "cpu" }, ] torchaudio = [ { index = "pytorch-cu130", extra = "vllm" }, { index = "pytorch-cu130", extra = "sglang" }, { index = "pytorch-cu130", extra = "fsdp" }, { index = "pytorch-cu130", extra = "megatron" }, { index = "pytorch-cpu", extra = "cpu" }, ] [[tool.uv.index]] name = "pytorch-cu130" url = "https://download.pytorch.org/whl/cu130" explicit = true [[tool.uv.index]] name = "pytorch-cpu" url = "https://download.pytorch.org/whl/cpu" explicit = true # verl wheelhouse: prebuilt cu130 / torch-2.11 / cp312 native wheels, published # side by side for linux_x86_64 and linux_aarch64 (uv picks by platform tag). # Only the packages routed above are pulled here (explicit = true); the rest stay # on PyPI. [[tool.uv.index]] name = "verl-wheelhouse" url = "https://verl-project.github.io/verl-wheelhouse/simple/" explicit = true