main
py 187 lines 8.31 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Tests for CUDA forward compatibility path logic in env_override.py.
4
5 Verifies the opt-in LD_LIBRARY_PATH manipulation for CUDA compat libs,
6 including env var parsing, path detection, and deduplication.
7 """
8
9 import os
10 from unittest.mock import patch
11
12 import pytest
13
14 # Import the functions directly (they're module-level in env_override)
15 # We must import them without triggering the module-level side effects,
16 # so we import the functions by name after the module is already loaded.
17 from vllm.env_override import (
18 _get_torch_cuda_version,
19 _maybe_set_cuda_compatibility_path,
20 )
21
22
23 class TestCudaCompatibilityEnvParsing:
24 """Test VLLM_ENABLE_CUDA_COMPATIBILITY env var parsing."""
25
26 def test_disabled_by_default(self, monkeypatch):
27 """Compat path is NOT set when env var is absent."""
28 monkeypatch.delenv("VLLM_ENABLE_CUDA_COMPATIBILITY", raising=False)
29 monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
30 _maybe_set_cuda_compatibility_path()
31 assert (
32 "LD_LIBRARY_PATH" not in os.environ
33 or os.environ.get("LD_LIBRARY_PATH", "") == ""
34 )
35
36 @pytest.mark.parametrize("value", ["0", "false", "False", "no", ""])
37 def test_disabled_values(self, monkeypatch, value):
38 """Various falsy values should not activate compat path."""
39 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", value)
40 monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
41 _maybe_set_cuda_compatibility_path()
42 # LD_LIBRARY_PATH should not be set (or remain empty)
43 ld_path = os.environ.get("LD_LIBRARY_PATH", "")
44 assert "compat" not in ld_path
45
46 @pytest.mark.parametrize("value", ["1", "true", "True", " 1 ", " TRUE "])
47 def test_enabled_values_with_valid_path(self, monkeypatch, tmp_path, value):
48 """Truthy values activate compat path when a valid path exists."""
49 compat_dir = tmp_path / "compat"
50 compat_dir.mkdir()
51 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", value)
52 monkeypatch.setenv("VLLM_CUDA_COMPATIBILITY_PATH", str(compat_dir))
53 monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
54 _maybe_set_cuda_compatibility_path()
55 ld_path = os.environ.get("LD_LIBRARY_PATH", "")
56 assert str(compat_dir) in ld_path
57
58
59 class TestCudaCompatibilityPathDetection:
60 """Test path detection: custom override, conda, default."""
61
62 def test_custom_path_override(self, monkeypatch, tmp_path):
63 """VLLM_CUDA_COMPATIBILITY_PATH takes highest priority."""
64 custom_dir = tmp_path / "my-compat"
65 custom_dir.mkdir()
66 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", "1")
67 monkeypatch.setenv("VLLM_CUDA_COMPATIBILITY_PATH", str(custom_dir))
68 monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
69 _maybe_set_cuda_compatibility_path()
70 ld_path = os.environ.get("LD_LIBRARY_PATH", "")
71 assert ld_path.startswith(str(custom_dir))
72
73 def test_conda_prefix_fallback(self, monkeypatch, tmp_path):
74 """Falls back to $CONDA_PREFIX/cuda-compat if custom not set."""
75 conda_dir = tmp_path / "conda-env"
76 compat_dir = conda_dir / "cuda-compat"
77 compat_dir.mkdir(parents=True)
78 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", "1")
79 monkeypatch.delenv("VLLM_CUDA_COMPATIBILITY_PATH", raising=False)
80 monkeypatch.setenv("CONDA_PREFIX", str(conda_dir))
81 monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
82 _maybe_set_cuda_compatibility_path()
83 ld_path = os.environ.get("LD_LIBRARY_PATH", "")
84 assert str(compat_dir) in ld_path
85
86 def test_no_valid_path_does_nothing(self, monkeypatch):
87 """When enabled but no valid path exists, LD_LIBRARY_PATH unchanged."""
88 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", "1")
89 monkeypatch.setenv("VLLM_CUDA_COMPATIBILITY_PATH", "/nonexistent/path")
90 monkeypatch.delenv("CONDA_PREFIX", raising=False)
91 monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
92 with patch("vllm.env_override._get_torch_cuda_version", return_value=None):
93 _maybe_set_cuda_compatibility_path()
94 assert os.environ.get("LD_LIBRARY_PATH", "") == ""
95
96 def test_default_cuda_path_fallback(self, monkeypatch, tmp_path):
97 """Falls back to /usr/local/cuda-{ver}/compat via torch version."""
98 fake_cuda = tmp_path / "cuda-12.8" / "compat"
99 fake_cuda.mkdir(parents=True)
100 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", "1")
101 monkeypatch.delenv("VLLM_CUDA_COMPATIBILITY_PATH", raising=False)
102 monkeypatch.delenv("CONDA_PREFIX", raising=False)
103 monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
104 with (
105 patch("vllm.env_override._get_torch_cuda_version", return_value="12.8"),
106 patch(
107 "vllm.env_override.os.path.isdir",
108 side_effect=lambda p: p == "/usr/local/cuda-12.8/compat"
109 or os.path.isdir(p),
110 ),
111 ):
112 _maybe_set_cuda_compatibility_path()
113 ld_path = os.environ.get("LD_LIBRARY_PATH", "")
114 assert "/usr/local/cuda-12.8/compat" in ld_path
115
116
117 class TestCudaCompatibilityLdPathManipulation:
118 """Test LD_LIBRARY_PATH prepend and deduplication logic."""
119
120 def test_prepends_to_empty_ld_path(self, monkeypatch, tmp_path):
121 """Compat path is set when LD_LIBRARY_PATH is empty."""
122 compat_dir = tmp_path / "compat"
123 compat_dir.mkdir()
124 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", "1")
125 monkeypatch.setenv("VLLM_CUDA_COMPATIBILITY_PATH", str(compat_dir))
126 monkeypatch.delenv("LD_LIBRARY_PATH", raising=False)
127 _maybe_set_cuda_compatibility_path()
128 assert os.environ["LD_LIBRARY_PATH"] == str(compat_dir)
129
130 def test_prepends_to_existing_ld_path(self, monkeypatch, tmp_path):
131 """Compat path is prepended before existing entries."""
132 compat_dir = tmp_path / "compat"
133 compat_dir.mkdir()
134 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", "1")
135 monkeypatch.setenv("VLLM_CUDA_COMPATIBILITY_PATH", str(compat_dir))
136 monkeypatch.setenv("LD_LIBRARY_PATH", "/usr/lib:/other/lib")
137 _maybe_set_cuda_compatibility_path()
138 ld_path = os.environ["LD_LIBRARY_PATH"]
139 parts = ld_path.split(os.pathsep)
140 assert parts[0] == str(compat_dir)
141 assert "/usr/lib" in parts
142 assert "/other/lib" in parts
143
144 def test_deduplicates_existing_compat_path(self, monkeypatch, tmp_path):
145 """If compat path already in LD_LIBRARY_PATH, move to front."""
146 compat_dir = tmp_path / "compat"
147 compat_dir.mkdir()
148 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", "1")
149 monkeypatch.setenv("VLLM_CUDA_COMPATIBILITY_PATH", str(compat_dir))
150 monkeypatch.setenv(
151 "LD_LIBRARY_PATH",
152 f"/usr/lib:{compat_dir}:/other/lib",
153 )
154 _maybe_set_cuda_compatibility_path()
155 ld_path = os.environ["LD_LIBRARY_PATH"]
156 parts = ld_path.split(os.pathsep)
157 assert parts[0] == str(compat_dir)
158 assert parts.count(str(compat_dir)) == 1
159
160 def test_already_at_front_is_noop(self, monkeypatch, tmp_path):
161 """If compat path is already first, don't modify LD_LIBRARY_PATH."""
162 compat_dir = tmp_path / "compat"
163 compat_dir.mkdir()
164 original = f"{compat_dir}:/usr/lib"
165 monkeypatch.setenv("VLLM_ENABLE_CUDA_COMPATIBILITY", "1")
166 monkeypatch.setenv("VLLM_CUDA_COMPATIBILITY_PATH", str(compat_dir))
167 monkeypatch.setenv("LD_LIBRARY_PATH", original)
168 _maybe_set_cuda_compatibility_path()
169 assert os.environ["LD_LIBRARY_PATH"] == original
170
171
172 class TestGetTorchCudaVersion:
173 """Test _get_torch_cuda_version() helper."""
174
175 def test_returns_string_when_torch_available(self):
176 """Should return a CUDA version string like '12.8'."""
177 version = _get_torch_cuda_version()
178 # torch is installed in vllm's environment
179 assert version is None or isinstance(version, str)
180
181 def test_returns_none_when_torch_missing(self):
182 """Should return None when torch is not importable."""
183 with patch(
184 "vllm.env_override.importlib.util.find_spec",
185 return_value=None,
186 ):
187 assert _get_torch_cuda_version() is None