[build-system] requires = ["setuptools>=61", "wheel"] build-backend = "setuptools.build_meta" [project] name = "unirl" dynamic = ["version"] description = "Unified multimodal RL training framework" readme = "README.md" # Floor follows upstream (3.12); cap because st_attn/vsa (sglang[diffusion]) # ship cp310-cp313 wheels only, with no sdist fallback. requires-python = ">=3.12,<3.14" license = { file = "LICENSE" } authors = [ { name = "unirl contributors" } ] dependencies = [ # Engine-agnostic only. torch intentionally absent: it enters via exactly # one engine extra below, which is what allows the per-engine CUDA stacks. "numpy>=1.24,<3", "ray[default]>=2.9", "diffusers>=0.38.0", # LTX-2 needs 0.38: transformer.forward gained sigma/audio_sigma/isolate_modalities/use_cross_timestep, connector gained padding_side, calculate_shift now pins image_seq_len to max_image_seq_len. (>=0.37 was already required for Qwen-Image RoPE text-len-from-mask; 0.38 verified to keep that + every other model API unchanged.) "hydra-core>=1.3", "omegaconf>=2.3", # Match the engine stacks' 5.6.x baseline; BAGEL vendor smoke passed on 5.6.0. "transformers>=5.6,<5.7", "huggingface-hub>=0.34", # Vendored Boogu-Image DiT (unirl/models/boogu_image/vendor/) imports einops # in transformer_boogu.py / attention_processor.py / rope.py. Pure-python, # engine-agnostic. "einops>=0.7", # 0.20 floor: older peft imports transformers cache symbols (HybridCache) # removed in 5.x and fails at import against the transformers pin above. # exclude_modules support (>=0.14) used by LoRA/NFT adapter injection. "peft>=0.20", "safetensors>=0.4", "Pillow>=10", "requests>=2.31", "psutil>=5.9", "tensordict>=0.5", ] # Parent/inline engines load this. Multi-stage StageDiffusionProc does not — # VLLMOmniHijack.hijack() reinstalls the same hook in spawn children. [project.entry-points."vllm_omni.general_plugins"] unirl_capture_flush = "unirl.rollout.engine.vllm_omni.plugin:register_capture_flush" [project.optional-dependencies] # sglang pinned to the upstream release the UniRL sglang patch package # (unirl/rollout/engine/sglang_diffusion/_patches/, LIN-365) is verified against # (sde/cps parity + SD3 GRPO e2e). sglang >=0.5.11 is CUDA-13-native: its # kernel stack (sglang-kernel, sgl-deep-gemm, cuda-python>=13) ships cu13 # binaries only, so this extra pins the matching torch +cu130 family and the # nvidia-*-cu13 pip wheels supply libcudart/libnvrtc.so.13 — a pure-wheel, # zero-compile install. On the driver-535 fleet CUDA-13 user code runs via # NVIDIA's forward-compat layer: bake cuda-compat-13 into the image and point # LD_LIBRARY_PATH at its compat dir (verified on H20 + driver 535). The sglang # wheel itself is manylinux_2_34, so the image base must be glibc>=2.34 # (upgrade pending; verified meanwhile via a 2.34-prefixed python). sglang = [ "sglang[diffusion]==0.5.12.post1 ; sys_platform == 'linux'", # sglang pins a prerelease; a first-party declaration makes it "explicitly # requested" under uv's default prerelease policy (scoped, not global). "flash-attn-4>=4.0.0b9,<5 ; sys_platform == 'linux'", # torchvision/torchaudio publish no PyPI linux wheels in this era — pin the # flavor matching sglang 0.5.12.post1's torch==2.11.0 from the cu130 index # (cu13-native like the rest of sglang's stack; verified on-pod under the # forward-compat layer). "torch==2.11.0+cu130 ; sys_platform == 'linux'", "torchvision==0.26.0+cu130 ; sys_platform == 'linux'", "torchaudio==2.11.0+cu130 ; sys_platform == 'linux'", # Flash-Linear-Attention (FLA) fast path for Qwen3.5 hybrid attention layers # (3 GDN + 1 full per 4). Without it, transformers falls back to the torch # implementation ("The fast path is not available because one of the required # library is not installed"). The [conv1d] extra pulls causal-conv1d, the # [tilelang] extra is already satisfied transitively. Pin to the version # verified on-pod with sglang 0.5.12.post1 / torch 2.11.0+cu130. "flash-linear-attention[conv1d]==0.5.1 ; sys_platform == 'linux'", "av>=14.2,<19 ; sys_platform == 'linux'", ] # CUDA 13: vllm >=0.26 wants a bare `torch==2.13.0` whose wheel is cu130, and a # +cu129 pin would resolve anyway, linking a CUDA-12 torch under a CUDA-13 vllm. # Prerelease, so installs need --prerelease=allow, plus sglang's compat layer. vllm = [ "vllm==0.27.0 ; sys_platform == 'linux'", "vllm-omni==0.27.0rc1 ; sys_platform == 'linux'", "torch==2.13.0+cu130 ; sys_platform == 'linux'", "torchvision==0.28.0+cu130 ; sys_platform == 'linux'", # torchaudio 2.11.0+cu130 is ABI-built against torch 2.11; there is no # torchaudio >=2.12 on the cu130 index, so this extra does not pin it. "av>=14.2,<19 ; sys_platform == 'linux'", ] # Exact upstream source snapshot that the fail-closed FastVideo patch package is # verified against. This is hao-ai-lab/FastVideo PR #1222's source commit, not # Zcchill/FastVideo's UniRL-modified fork; UniRL re-hosts the required RL # contracts under unirl/rollout/engine/fastvideo/_patches/. fastvideo = [ "fastvideo @ git+https://github.com/hao-ai-lab/FastVideo.git@2095477eac7e289c7a7ab13acb367ca60687c304 ; sys_platform == 'linux'", ] train = [ "wandb>=0.16,<0.20", "aiohttp>=3.9", # MathVerifyRewardScorer (unirl/reward/local/mathverify.py) — the paper's grader. "math-verify>=0.6", ] cosmos3 = [ "diffusers>=0.39", ] infer = [ "accelerate>=0.30", # Janus-Pro's vendored SigLIP implementation imports timm directly. "timm>=0.9.16", ] eval = [ "torchvision>=0.16", "paddlepaddle==3.2.2", "paddleocr==3.5.0", "python-Levenshtein>=0.27", ] # VeOmni's torch-native distributed layer (FSDP2/EP parallelize), consumed # exclusively through unirl.train.backend.veomni._compat — a selective import # that never executes veomni/__init__.py or veomni/models/__init__.py, so the # transformers-locked model zoo inside the wheel stays dormant. The shim's # import closure is audited per release (tests/test_compat_import.py). veomni = [ "veomni==0.1.11 ; sys_platform == 'linux'", ] dev = [ "pytest>=7.4", "pytest-cov>=4.1", "ruff>=0.6", "pre-commit>=3.6", ] # Offline converters under datasets/ (see datasets/README.md). None of this is # imported by unirl/, which is why it is not a base dependency: you install it in # a checkout to cook a dataset, then train without it. Reading a Hub dataset needs # `datasets`; arxivqa_mc reads Vero's parquet shards through pyarrow directly # (deliberately not via `datasets`, to keep the nested structs unreshaped); the # LeRobot droid100 prep reads parquet metadata with pandas and decodes video with # av. The av bound matches the sglang/vllm extras so installing both cannot # conflict. # # torch is deliberately NOT here even though droid100 imports it (clip resize + # .pt writes): adding it would put a second torch declaration outside the engine # extras, and pinning exactly one is what lets each engine carry its own CUDA # stack. droid100 is the only converter that needs it; datasets/droid100/README.md # says so, and every other converter runs in a bare dataset-prep venv. dataset-prep = [ "datasets>=2.14", "pandas>=2", "pyarrow>=14", "av>=14.2,<19", ] [tool.uv] conflicts = [ [{ extra = "sglang" }, { extra = "vllm" }], ] # Local-version anchors only exist on the CUDA indexes; best-match lets every # other package take its newest from PyPI while +cuXXX outranks same-release # PyPI builds where it matters. index-strategy = "unsafe-best-match" # sglang 0.5.12.post1 pins transformers==5.6.0 but leaves its `kernels` # integration unpinned; kernels >=0.15 changed LayerRepository's signature and # breaks transformers 5.6.0 at import time. Hold it to transformers' own # declared bound. # # transformers 5.6.0 accepts tokenizers<=0.23.0, but tokenizers 0.23.0(rc) # dropped the `cls` kwarg from RobertaProcessing.__new__, so deserializing a # saved CLIP tokenizer.json raises TypeError at rollout init (the SD3 text # encoders, e2e-confirmed on H20). The sglang install's --prerelease=allow flag # (see INSTALL.md) otherwise selects 0.23.0rc*; this override outranks it AND # guards against 0.23.0 shipping stable. tokenizers 0.22.2 verified on both # engine venvs. override-dependencies = [ "kernels>=0.12,<0.13", "tokenizers>=0.22,<0.23", # sglang[diffusion] hard-pins diffusers==0.37.0 (still pinned on sglang main), # colliding with the base diffusers>=0.38.0 floor (#91); override to the floor # so ".[sglang,...]" stays solvable (same tactic as kernels/tokenizers above). "diffusers>=0.38.0", ] environments = [ "sys_platform == 'linux' and platform_machine == 'x86_64'", ] # Only fires if resolution falls back to the sdist: build-requires omits numpy, # and the build-env torch must match the runtime flavor. [tool.uv.extra-build-dependencies] vllm = [ "numpy", "torch==2.13.0+cu130", ] # Non-explicit and ahead of the implicit PyPI default: participates in # resolution for ALL packages, including the engines' transitive torch pins. # Both extras are cu13, so a cu129 index here would mix flavors in unpinned deps. [[tool.uv.index]] name = "torch-cu130" url = "https://download.pytorch.org/whl/cu130" [tool.setuptools] include-package-data = true [tool.setuptools.dynamic] version = { attr = "unirl.__version__" } [tool.setuptools.packages.find] where = ["."] include = [ "unirl", "unirl.*", ] [tool.setuptools.package-data] "unirl.models.janus_pro.vendor" = ["LICENSE-CODE", "VENDOR_COMMIT.txt"] [tool.ruff] line-length = 120 target-version = "py312" # unirl-reward-service/ is a vendored standalone sub-project with its own pyproject and # lint/test tooling; it is not governed by this repo's lint. Its vendored # upstream model code carries # semantically-meaningful trailing whitespace (VideoAlign prompt templates) that # must not be auto-stripped. extend-exclude = [ "unirl-reward-service", "unirl/models/bagel/vendor", "unirl/models/janus_pro/vendor", "unirl/models/sensenova_u1/vendor", ] [tool.ruff.lint] select = ["E", "F", "W", "I"] ignore = ["E501"] [tool.ruff.lint.isort] known-first-party = ["unirl"] [tool.ruff.format] quote-style = "double"