viettelcloud-aiplatform 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- viettelcloud/__init__.py +1 -0
- viettelcloud/aiplatform/__init__.py +15 -0
- viettelcloud/aiplatform/common/__init__.py +0 -0
- viettelcloud/aiplatform/common/constants.py +22 -0
- viettelcloud/aiplatform/common/types.py +28 -0
- viettelcloud/aiplatform/common/utils.py +40 -0
- viettelcloud/aiplatform/hub/OWNERS +14 -0
- viettelcloud/aiplatform/hub/__init__.py +25 -0
- viettelcloud/aiplatform/hub/api/__init__.py +13 -0
- viettelcloud/aiplatform/hub/api/_proxy_client.py +355 -0
- viettelcloud/aiplatform/hub/api/model_registry_client.py +561 -0
- viettelcloud/aiplatform/hub/api/model_registry_client_test.py +462 -0
- viettelcloud/aiplatform/optimizer/__init__.py +45 -0
- viettelcloud/aiplatform/optimizer/api/__init__.py +0 -0
- viettelcloud/aiplatform/optimizer/api/optimizer_client.py +248 -0
- viettelcloud/aiplatform/optimizer/backends/__init__.py +13 -0
- viettelcloud/aiplatform/optimizer/backends/base.py +77 -0
- viettelcloud/aiplatform/optimizer/backends/kubernetes/__init__.py +13 -0
- viettelcloud/aiplatform/optimizer/backends/kubernetes/backend.py +563 -0
- viettelcloud/aiplatform/optimizer/backends/kubernetes/utils.py +112 -0
- viettelcloud/aiplatform/optimizer/constants/__init__.py +13 -0
- viettelcloud/aiplatform/optimizer/constants/constants.py +59 -0
- viettelcloud/aiplatform/optimizer/types/__init__.py +13 -0
- viettelcloud/aiplatform/optimizer/types/algorithm_types.py +87 -0
- viettelcloud/aiplatform/optimizer/types/optimization_types.py +135 -0
- viettelcloud/aiplatform/optimizer/types/search_types.py +95 -0
- viettelcloud/aiplatform/py.typed +0 -0
- viettelcloud/aiplatform/trainer/__init__.py +82 -0
- viettelcloud/aiplatform/trainer/api/__init__.py +3 -0
- viettelcloud/aiplatform/trainer/api/trainer_client.py +277 -0
- viettelcloud/aiplatform/trainer/api/trainer_client_test.py +72 -0
- viettelcloud/aiplatform/trainer/backends/__init__.py +0 -0
- viettelcloud/aiplatform/trainer/backends/base.py +94 -0
- viettelcloud/aiplatform/trainer/backends/container/adapters/base.py +195 -0
- viettelcloud/aiplatform/trainer/backends/container/adapters/docker.py +231 -0
- viettelcloud/aiplatform/trainer/backends/container/adapters/podman.py +258 -0
- viettelcloud/aiplatform/trainer/backends/container/backend.py +668 -0
- viettelcloud/aiplatform/trainer/backends/container/backend_test.py +867 -0
- viettelcloud/aiplatform/trainer/backends/container/runtime_loader.py +631 -0
- viettelcloud/aiplatform/trainer/backends/container/runtime_loader_test.py +637 -0
- viettelcloud/aiplatform/trainer/backends/container/types.py +67 -0
- viettelcloud/aiplatform/trainer/backends/container/utils.py +213 -0
- viettelcloud/aiplatform/trainer/backends/kubernetes/__init__.py +0 -0
- viettelcloud/aiplatform/trainer/backends/kubernetes/backend.py +710 -0
- viettelcloud/aiplatform/trainer/backends/kubernetes/backend_test.py +1344 -0
- viettelcloud/aiplatform/trainer/backends/kubernetes/constants.py +15 -0
- viettelcloud/aiplatform/trainer/backends/kubernetes/utils.py +636 -0
- viettelcloud/aiplatform/trainer/backends/kubernetes/utils_test.py +582 -0
- viettelcloud/aiplatform/trainer/backends/localprocess/__init__.py +0 -0
- viettelcloud/aiplatform/trainer/backends/localprocess/backend.py +306 -0
- viettelcloud/aiplatform/trainer/backends/localprocess/backend_test.py +501 -0
- viettelcloud/aiplatform/trainer/backends/localprocess/constants.py +90 -0
- viettelcloud/aiplatform/trainer/backends/localprocess/job.py +184 -0
- viettelcloud/aiplatform/trainer/backends/localprocess/types.py +52 -0
- viettelcloud/aiplatform/trainer/backends/localprocess/utils.py +302 -0
- viettelcloud/aiplatform/trainer/constants/__init__.py +0 -0
- viettelcloud/aiplatform/trainer/constants/constants.py +179 -0
- viettelcloud/aiplatform/trainer/options/__init__.py +52 -0
- viettelcloud/aiplatform/trainer/options/common.py +55 -0
- viettelcloud/aiplatform/trainer/options/kubernetes.py +502 -0
- viettelcloud/aiplatform/trainer/options/kubernetes_test.py +259 -0
- viettelcloud/aiplatform/trainer/options/localprocess.py +20 -0
- viettelcloud/aiplatform/trainer/test/common.py +22 -0
- viettelcloud/aiplatform/trainer/types/__init__.py +0 -0
- viettelcloud/aiplatform/trainer/types/types.py +517 -0
- viettelcloud/aiplatform/trainer/types/types_test.py +115 -0
- viettelcloud_aiplatform-0.3.0.dist-info/METADATA +226 -0
- viettelcloud_aiplatform-0.3.0.dist-info/RECORD +71 -0
- viettelcloud_aiplatform-0.3.0.dist-info/WHEEL +4 -0
- viettelcloud_aiplatform-0.3.0.dist-info/licenses/LICENSE +201 -0
- viettelcloud_aiplatform-0.3.0.dist-info/licenses/NOTICE +36 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Copyright 2025 The Kubeflow Authors.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Unit tests for Kubernetes options."""
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from viettelcloud.aiplatform.trainer.backends.kubernetes.backend import KubernetesBackend
|
|
20
|
+
from viettelcloud.aiplatform.trainer.backends.localprocess.backend import LocalProcessBackend
|
|
21
|
+
from viettelcloud.aiplatform.trainer.options import (
|
|
22
|
+
Annotations,
|
|
23
|
+
ContainerOverride,
|
|
24
|
+
Labels,
|
|
25
|
+
Name,
|
|
26
|
+
PodTemplateOverride,
|
|
27
|
+
PodTemplateOverrides,
|
|
28
|
+
SpecAnnotations,
|
|
29
|
+
SpecLabels,
|
|
30
|
+
TrainerArgs,
|
|
31
|
+
TrainerCommand,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def mock_kubernetes_backend():
|
|
37
|
+
"""Mock Kubernetes backend for testing."""
|
|
38
|
+
from unittest.mock import Mock
|
|
39
|
+
|
|
40
|
+
backend = Mock(spec=KubernetesBackend)
|
|
41
|
+
backend.__class__ = KubernetesBackend
|
|
42
|
+
return backend
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture
|
|
46
|
+
def mock_localprocess_backend():
|
|
47
|
+
"""Mock LocalProcess backend for testing."""
|
|
48
|
+
from unittest.mock import MagicMock
|
|
49
|
+
|
|
50
|
+
# Create a proper mock that isinstance checks will work with
|
|
51
|
+
backend = MagicMock(spec=LocalProcessBackend)
|
|
52
|
+
# Make type(backend).__name__ return the correct class name
|
|
53
|
+
type(backend).__name__ = "LocalProcessBackend"
|
|
54
|
+
return backend
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestKubernetesOptionBackendValidation:
|
|
58
|
+
"""Test that Kubernetes options validate backend compatibility."""
|
|
59
|
+
|
|
60
|
+
@pytest.mark.parametrize(
|
|
61
|
+
"option_class,option_args",
|
|
62
|
+
[
|
|
63
|
+
(Labels, {"app": "test", "version": "v1"}),
|
|
64
|
+
(Annotations, {"description": "test job"}),
|
|
65
|
+
(SpecLabels, {"app": "training"}),
|
|
66
|
+
(SpecAnnotations, {"prometheus.io/scrape": "true"}),
|
|
67
|
+
(TrainerCommand, ["python", "train.py"]),
|
|
68
|
+
(TrainerArgs, ["--epochs", "10"]),
|
|
69
|
+
],
|
|
70
|
+
)
|
|
71
|
+
def test_kubernetes_options_reject_wrong_backend(
|
|
72
|
+
self, option_class, option_args, mock_localprocess_backend
|
|
73
|
+
):
|
|
74
|
+
"""Test Kubernetes-specific options reject non-Kubernetes backends."""
|
|
75
|
+
if option_class == TrainerCommand:
|
|
76
|
+
option = option_class(command=option_args)
|
|
77
|
+
elif option_class == TrainerArgs:
|
|
78
|
+
option = option_class(args=option_args)
|
|
79
|
+
else:
|
|
80
|
+
option = option_class(option_args)
|
|
81
|
+
|
|
82
|
+
job_spec = {}
|
|
83
|
+
|
|
84
|
+
with pytest.raises(ValueError) as exc_info:
|
|
85
|
+
option(job_spec, None, mock_localprocess_backend)
|
|
86
|
+
|
|
87
|
+
assert "not compatible with" in str(exc_info.value)
|
|
88
|
+
assert "LocalProcessBackend" in str(exc_info.value)
|
|
89
|
+
|
|
90
|
+
def test_pod_template_overrides_rejects_wrong_backend(self, mock_localprocess_backend):
|
|
91
|
+
"""Test PodTemplateOverrides rejects non-Kubernetes backends."""
|
|
92
|
+
override = PodTemplateOverride(target_jobs=["node"])
|
|
93
|
+
option = PodTemplateOverrides(override)
|
|
94
|
+
|
|
95
|
+
job_spec = {}
|
|
96
|
+
|
|
97
|
+
with pytest.raises(ValueError) as exc_info:
|
|
98
|
+
option(job_spec, None, mock_localprocess_backend)
|
|
99
|
+
|
|
100
|
+
assert "not compatible with" in str(exc_info.value)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestKubernetesOptionApplication:
|
|
104
|
+
"""Test Kubernetes option application behavior."""
|
|
105
|
+
|
|
106
|
+
@pytest.mark.parametrize(
|
|
107
|
+
"option_class,option_args,expected_spec",
|
|
108
|
+
[
|
|
109
|
+
(
|
|
110
|
+
Labels,
|
|
111
|
+
{"app": "test", "version": "v1"},
|
|
112
|
+
{"metadata": {"labels": {"app": "test", "version": "v1"}}},
|
|
113
|
+
),
|
|
114
|
+
(
|
|
115
|
+
Annotations,
|
|
116
|
+
{"description": "test job"},
|
|
117
|
+
{"metadata": {"annotations": {"description": "test job"}}},
|
|
118
|
+
),
|
|
119
|
+
(
|
|
120
|
+
SpecLabels,
|
|
121
|
+
{"app": "training", "version": "v1.0"},
|
|
122
|
+
{"spec": {"labels": {"app": "training", "version": "v1.0"}}},
|
|
123
|
+
),
|
|
124
|
+
(
|
|
125
|
+
SpecAnnotations,
|
|
126
|
+
{"prometheus.io/scrape": "true"},
|
|
127
|
+
{"spec": {"annotations": {"prometheus.io/scrape": "true"}}},
|
|
128
|
+
),
|
|
129
|
+
(Name, "custom-job-name", {"metadata": {"name": "custom-job-name"}}),
|
|
130
|
+
(
|
|
131
|
+
TrainerCommand,
|
|
132
|
+
["python", "train.py"],
|
|
133
|
+
{"spec": {"trainer": {"command": ["python", "train.py"]}}},
|
|
134
|
+
),
|
|
135
|
+
(
|
|
136
|
+
TrainerArgs,
|
|
137
|
+
["--epochs", "10"],
|
|
138
|
+
{"spec": {"trainer": {"args": ["--epochs", "10"]}}},
|
|
139
|
+
),
|
|
140
|
+
],
|
|
141
|
+
)
|
|
142
|
+
def test_option_application(
|
|
143
|
+
self, option_class, option_args, expected_spec, mock_kubernetes_backend
|
|
144
|
+
):
|
|
145
|
+
"""Test each option applies correctly to job spec with Kubernetes backend."""
|
|
146
|
+
if option_class == TrainerCommand:
|
|
147
|
+
option = option_class(command=option_args)
|
|
148
|
+
elif option_class == TrainerArgs:
|
|
149
|
+
option = option_class(args=option_args)
|
|
150
|
+
else:
|
|
151
|
+
option = option_class(option_args)
|
|
152
|
+
|
|
153
|
+
job_spec = {}
|
|
154
|
+
option(job_spec, None, mock_kubernetes_backend)
|
|
155
|
+
|
|
156
|
+
assert job_spec == expected_spec
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class TestTrainerOptionValidation:
|
|
160
|
+
"""Test validation of trainer-specific options."""
|
|
161
|
+
|
|
162
|
+
@pytest.mark.parametrize(
|
|
163
|
+
"option_class,option_args,trainer_type,should_fail",
|
|
164
|
+
[
|
|
165
|
+
# Validation failures
|
|
166
|
+
(TrainerCommand, ["python", "train.py"], "CustomTrainer", True),
|
|
167
|
+
(TrainerArgs, ["--epochs", "10"], "CustomTrainer", True),
|
|
168
|
+
(TrainerCommand, ["python", "train.py"], "BuiltinTrainer", True),
|
|
169
|
+
(TrainerArgs, ["--epochs", "10"], "BuiltinTrainer", True),
|
|
170
|
+
# Successful applications
|
|
171
|
+
(TrainerCommand, ["python", "train.py"], "CustomTrainerContainer", False),
|
|
172
|
+
(TrainerArgs, ["--epochs", "10"], "CustomTrainerContainer", False),
|
|
173
|
+
],
|
|
174
|
+
)
|
|
175
|
+
def test_trainer_option_validation(
|
|
176
|
+
self, option_class, option_args, trainer_type, should_fail, mock_kubernetes_backend
|
|
177
|
+
):
|
|
178
|
+
"""Test trainer option validation with different trainer types."""
|
|
179
|
+
from viettelcloud.aiplatform.trainer.types.types import (
|
|
180
|
+
BuiltinTrainer,
|
|
181
|
+
CustomTrainer,
|
|
182
|
+
CustomTrainerContainer,
|
|
183
|
+
TorchTuneConfig,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Create appropriate trainer instance
|
|
187
|
+
if trainer_type == "CustomTrainer":
|
|
188
|
+
|
|
189
|
+
def dummy_func():
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
trainer = CustomTrainer(func=dummy_func)
|
|
193
|
+
elif trainer_type == "BuiltinTrainer":
|
|
194
|
+
trainer = BuiltinTrainer(config=TorchTuneConfig())
|
|
195
|
+
else: # CustomTrainerContainer
|
|
196
|
+
trainer = CustomTrainerContainer(image="custom-image:latest")
|
|
197
|
+
|
|
198
|
+
# Create option
|
|
199
|
+
if option_class == TrainerCommand:
|
|
200
|
+
option = option_class(command=option_args)
|
|
201
|
+
else: # TrainerArgs
|
|
202
|
+
option = option_class(args=option_args)
|
|
203
|
+
|
|
204
|
+
job_spec = {}
|
|
205
|
+
|
|
206
|
+
if should_fail:
|
|
207
|
+
with pytest.raises(ValueError) as exc_info:
|
|
208
|
+
option(job_spec, trainer, mock_kubernetes_backend)
|
|
209
|
+
assert "TrainerCommand can only be used with CustomTrainerContainer" in str(
|
|
210
|
+
exc_info.value
|
|
211
|
+
) or "TrainerArgs can only be used with CustomTrainerContainer" in str(exc_info.value)
|
|
212
|
+
else:
|
|
213
|
+
option(job_spec, trainer, mock_kubernetes_backend)
|
|
214
|
+
if option_class == TrainerCommand:
|
|
215
|
+
assert job_spec["spec"]["trainer"]["command"] == option_args
|
|
216
|
+
else:
|
|
217
|
+
assert job_spec["spec"]["trainer"]["args"] == option_args
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class TestContainerOverride:
|
|
221
|
+
"""Test ContainerOverride validation."""
|
|
222
|
+
|
|
223
|
+
@pytest.mark.parametrize(
|
|
224
|
+
"kwargs,expected_error",
|
|
225
|
+
[
|
|
226
|
+
({"name": ""}, "Container name must be a non-empty string"),
|
|
227
|
+
(
|
|
228
|
+
{"name": "trainer", "env": [{"invalid": "structure"}]},
|
|
229
|
+
"Each env entry must have a 'name' key",
|
|
230
|
+
),
|
|
231
|
+
(
|
|
232
|
+
{"name": "trainer", "volume_mounts": [{"name": "vol"}]},
|
|
233
|
+
"Each volume_mounts entry must have a 'mountPath' key",
|
|
234
|
+
),
|
|
235
|
+
],
|
|
236
|
+
)
|
|
237
|
+
def test_container_override_validation(self, kwargs, expected_error):
|
|
238
|
+
"""Test ContainerOverride validates inputs correctly."""
|
|
239
|
+
with pytest.raises(ValueError) as exc_info:
|
|
240
|
+
ContainerOverride(**kwargs)
|
|
241
|
+
assert expected_error in str(exc_info.value)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class TestPodTemplateOverrides:
|
|
245
|
+
"""Test PodTemplateOverrides functionality."""
|
|
246
|
+
|
|
247
|
+
def test_pod_template_overrides_basic(self, mock_kubernetes_backend):
|
|
248
|
+
"""Test basic PodTemplateOverrides application."""
|
|
249
|
+
|
|
250
|
+
override = PodTemplateOverride(target_jobs=["node"])
|
|
251
|
+
option = PodTemplateOverrides(override)
|
|
252
|
+
|
|
253
|
+
job_spec = {}
|
|
254
|
+
option(job_spec, None, mock_kubernetes_backend)
|
|
255
|
+
|
|
256
|
+
assert "spec" in job_spec
|
|
257
|
+
assert "podTemplateOverrides" in job_spec["spec"]
|
|
258
|
+
assert len(job_spec["spec"]["podTemplateOverrides"]) == 1
|
|
259
|
+
assert job_spec["spec"]["podTemplateOverrides"][0]["targetJobs"] == [{"name": "node"}]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright 2025 The Kubeflow Authors.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""LocalProcess-specific training options for the Kubeflow Trainer SDK.
|
|
16
|
+
|
|
17
|
+
TODO: Add LocalProcess options (ProcessTimeout, WorkingDirectory, etc.) in future iteration.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# TODO: Implement LocalProcess options using LocalProcessCompatible base class
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Shared test utilities and types for Kubeflow Trainer tests.
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
# Common status constants
|
|
7
|
+
SUCCESS = "success"
|
|
8
|
+
FAILED = "Failed"
|
|
9
|
+
DEFAULT_NAMESPACE = "default"
|
|
10
|
+
TIMEOUT = "timeout"
|
|
11
|
+
RUNTIME = "runtime"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class TestCase:
|
|
16
|
+
name: str
|
|
17
|
+
expected_status: str = SUCCESS
|
|
18
|
+
config: dict[str, Any] = field(default_factory=dict)
|
|
19
|
+
expected_output: Optional[Any] = None
|
|
20
|
+
expected_error: Optional[type[Exception]] = None
|
|
21
|
+
# Prevent pytest from collecting this dataclass as a test
|
|
22
|
+
__test__ = False
|
|
File without changes
|