wandb 0.17.0rc2__py3-none-any.whl → 0.17.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- wandb/__init__.py +1 -2
- wandb/apis/importers/internals/internal.py +0 -1
- wandb/apis/importers/wandb.py +12 -7
- wandb/apis/internal.py +0 -3
- wandb/apis/public/api.py +213 -79
- wandb/apis/public/artifacts.py +335 -100
- wandb/apis/public/files.py +9 -9
- wandb/apis/public/jobs.py +16 -4
- wandb/apis/public/projects.py +26 -28
- wandb/apis/public/query_generator.py +1 -1
- wandb/apis/public/runs.py +163 -65
- wandb/apis/public/sweeps.py +2 -2
- wandb/apis/reports/__init__.py +1 -7
- wandb/apis/reports/v1/__init__.py +5 -27
- wandb/apis/reports/v2/__init__.py +7 -19
- wandb/apis/workspaces/__init__.py +8 -0
- wandb/beta/workflows.py +8 -3
- wandb/cli/cli.py +131 -59
- wandb/docker/__init__.py +1 -1
- wandb/errors/term.py +10 -2
- wandb/filesync/step_checksum.py +1 -4
- wandb/filesync/step_prepare.py +4 -24
- wandb/filesync/step_upload.py +5 -107
- wandb/filesync/upload_job.py +0 -76
- wandb/integration/gym/__init__.py +35 -15
- wandb/integration/openai/fine_tuning.py +21 -3
- wandb/integration/prodigy/prodigy.py +1 -1
- wandb/jupyter.py +16 -17
- wandb/plot/pr_curve.py +2 -1
- wandb/plot/roc_curve.py +2 -1
- wandb/{plots → plot}/utils.py +13 -25
- wandb/proto/v3/wandb_internal_pb2.py +54 -54
- wandb/proto/v3/wandb_settings_pb2.py +2 -2
- wandb/proto/v3/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v4/wandb_internal_pb2.py +54 -54
- wandb/proto/v4/wandb_settings_pb2.py +2 -2
- wandb/proto/v4/wandb_telemetry_pb2.py +10 -10
- wandb/proto/v5/wandb_base_pb2.py +30 -0
- wandb/proto/v5/wandb_internal_pb2.py +355 -0
- wandb/proto/v5/wandb_server_pb2.py +63 -0
- wandb/proto/v5/wandb_settings_pb2.py +45 -0
- wandb/proto/v5/wandb_telemetry_pb2.py +41 -0
- wandb/proto/wandb_base_pb2.py +2 -0
- wandb/proto/wandb_deprecated.py +9 -1
- wandb/proto/wandb_generate_deprecated.py +34 -0
- wandb/proto/{wandb_internal_codegen.py → wandb_generate_proto.py} +1 -35
- wandb/proto/wandb_internal_pb2.py +2 -0
- wandb/proto/wandb_server_pb2.py +2 -0
- wandb/proto/wandb_settings_pb2.py +2 -0
- wandb/proto/wandb_telemetry_pb2.py +2 -0
- wandb/sdk/artifacts/artifact.py +68 -22
- wandb/sdk/artifacts/artifact_manifest.py +1 -1
- wandb/sdk/artifacts/artifact_manifest_entry.py +6 -3
- wandb/sdk/artifacts/artifact_manifests/artifact_manifest_v1.py +1 -1
- wandb/sdk/artifacts/artifact_saver.py +1 -10
- wandb/sdk/artifacts/storage_handlers/local_file_handler.py +6 -2
- wandb/sdk/artifacts/storage_handlers/multi_handler.py +1 -1
- wandb/sdk/artifacts/storage_handlers/tracking_handler.py +6 -4
- wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +2 -42
- wandb/sdk/artifacts/storage_policy.py +1 -12
- wandb/sdk/data_types/image.py +1 -1
- wandb/sdk/data_types/video.py +4 -2
- wandb/sdk/interface/interface.py +13 -0
- wandb/sdk/interface/interface_shared.py +1 -1
- wandb/sdk/internal/file_pusher.py +2 -5
- wandb/sdk/internal/file_stream.py +6 -19
- wandb/sdk/internal/internal_api.py +148 -136
- wandb/sdk/internal/job_builder.py +207 -135
- wandb/sdk/internal/progress.py +0 -28
- wandb/sdk/internal/sender.py +102 -39
- wandb/sdk/internal/settings_static.py +8 -1
- wandb/sdk/internal/system/assets/trainium.py +3 -3
- wandb/sdk/internal/system/system_info.py +4 -2
- wandb/sdk/internal/update.py +1 -1
- wandb/sdk/launch/__init__.py +9 -1
- wandb/sdk/launch/_launch.py +4 -24
- wandb/sdk/launch/_launch_add.py +1 -3
- wandb/sdk/launch/_project_spec.py +184 -224
- wandb/sdk/launch/agent/agent.py +58 -18
- wandb/sdk/launch/agent/config.py +0 -3
- wandb/sdk/launch/builder/abstract.py +67 -0
- wandb/sdk/launch/builder/build.py +165 -576
- wandb/sdk/launch/builder/context_manager.py +235 -0
- wandb/sdk/launch/builder/docker_builder.py +7 -23
- wandb/sdk/launch/builder/kaniko_builder.py +10 -23
- wandb/sdk/launch/builder/templates/dockerfile.py +92 -0
- wandb/sdk/launch/create_job.py +51 -45
- wandb/sdk/launch/environment/aws_environment.py +26 -1
- wandb/sdk/launch/inputs/files.py +148 -0
- wandb/sdk/launch/inputs/internal.py +224 -0
- wandb/sdk/launch/inputs/manage.py +95 -0
- wandb/sdk/launch/runner/abstract.py +2 -2
- wandb/sdk/launch/runner/kubernetes_monitor.py +45 -12
- wandb/sdk/launch/runner/kubernetes_runner.py +6 -8
- wandb/sdk/launch/runner/local_container.py +2 -3
- wandb/sdk/launch/runner/local_process.py +8 -29
- wandb/sdk/launch/runner/sagemaker_runner.py +20 -14
- wandb/sdk/launch/runner/vertex_runner.py +8 -7
- wandb/sdk/launch/sweeps/scheduler.py +2 -0
- wandb/sdk/launch/sweeps/utils.py +2 -2
- wandb/sdk/launch/utils.py +16 -138
- wandb/sdk/lib/_settings_toposort_generated.py +2 -5
- wandb/sdk/lib/apikey.py +4 -2
- wandb/sdk/lib/config_util.py +3 -3
- wandb/sdk/lib/proto_util.py +22 -1
- wandb/sdk/lib/redirect.py +1 -1
- wandb/sdk/service/service.py +2 -1
- wandb/sdk/service/streams.py +5 -5
- wandb/sdk/wandb_init.py +25 -59
- wandb/sdk/wandb_login.py +28 -25
- wandb/sdk/wandb_run.py +112 -45
- wandb/sdk/wandb_settings.py +33 -64
- wandb/sdk/wandb_watch.py +1 -1
- wandb/sklearn/plot/classifier.py +4 -6
- wandb/sync/sync.py +2 -2
- wandb/testing/relay.py +32 -17
- wandb/util.py +36 -37
- wandb/wandb_agent.py +3 -3
- wandb/wandb_controller.py +3 -2
- {wandb-0.17.0rc2.dist-info → wandb-0.17.1.dist-info}/METADATA +7 -9
- {wandb-0.17.0rc2.dist-info → wandb-0.17.1.dist-info}/RECORD +124 -146
- {wandb-0.17.0rc2.dist-info → wandb-0.17.1.dist-info}/WHEEL +1 -1
- wandb/apis/reports/v1/_blocks.py +0 -1406
- wandb/apis/reports/v1/_helpers.py +0 -70
- wandb/apis/reports/v1/_panels.py +0 -1282
- wandb/apis/reports/v1/_templates.py +0 -478
- wandb/apis/reports/v1/blocks.py +0 -27
- wandb/apis/reports/v1/helpers.py +0 -2
- wandb/apis/reports/v1/mutations.py +0 -66
- wandb/apis/reports/v1/panels.py +0 -17
- wandb/apis/reports/v1/report.py +0 -268
- wandb/apis/reports/v1/runset.py +0 -144
- wandb/apis/reports/v1/templates.py +0 -7
- wandb/apis/reports/v1/util.py +0 -406
- wandb/apis/reports/v1/validators.py +0 -131
- wandb/apis/reports/v2/blocks.py +0 -25
- wandb/apis/reports/v2/expr_parsing.py +0 -257
- wandb/apis/reports/v2/gql.py +0 -68
- wandb/apis/reports/v2/interface.py +0 -1911
- wandb/apis/reports/v2/internal.py +0 -867
- wandb/apis/reports/v2/metrics.py +0 -6
- wandb/apis/reports/v2/panels.py +0 -15
- wandb/catboost/__init__.py +0 -9
- wandb/fastai/__init__.py +0 -9
- wandb/keras/__init__.py +0 -19
- wandb/lightgbm/__init__.py +0 -9
- wandb/plots/__init__.py +0 -6
- wandb/plots/explain_text.py +0 -36
- wandb/plots/heatmap.py +0 -81
- wandb/plots/named_entity.py +0 -43
- wandb/plots/part_of_speech.py +0 -50
- wandb/plots/plot_definitions.py +0 -768
- wandb/plots/precision_recall.py +0 -121
- wandb/plots/roc.py +0 -103
- wandb/sacred/__init__.py +0 -3
- wandb/xgboost/__init__.py +0 -9
- {wandb-0.17.0rc2.dist-info → wandb-0.17.1.dist-info}/entry_points.txt +0 -0
- {wandb-0.17.0rc2.dist-info → wandb-0.17.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,235 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import shutil
|
4
|
+
import tempfile
|
5
|
+
from typing import Tuple
|
6
|
+
|
7
|
+
from wandb.sdk.launch._project_spec import LaunchProject
|
8
|
+
from wandb.sdk.launch.builder.build import image_tag_from_dockerfile_and_source
|
9
|
+
from wandb.sdk.launch.errors import LaunchError
|
10
|
+
from wandb.sdk.launch.utils import get_current_python_version
|
11
|
+
|
12
|
+
from .build import (
|
13
|
+
_WANDB_DOCKERFILE_NAME,
|
14
|
+
get_base_setup,
|
15
|
+
get_docker_user,
|
16
|
+
get_entrypoint_setup,
|
17
|
+
get_requirements_section,
|
18
|
+
get_user_setup,
|
19
|
+
)
|
20
|
+
from .templates.dockerfile import DOCKERFILE_TEMPLATE
|
21
|
+
|
22
|
+
_logger = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
|
25
|
+
class BuildContextManager:
|
26
|
+
"""Creates a build context for a container image from job source code.
|
27
|
+
|
28
|
+
The dockerfile and build context may be specified by the job itself. If not,
|
29
|
+
the behavior for creating the build context is as follows:
|
30
|
+
|
31
|
+
- If a Dockerfile.wandb is found adjacent to the entrypoint, the directory
|
32
|
+
containing the entrypoint is used as the build context and Dockerfile.wandb
|
33
|
+
is used as the Dockerfile.
|
34
|
+
|
35
|
+
- If `override_dockerfile` is set on the LaunchProject, the directory
|
36
|
+
containing the Dockerfile is used as the build context and the Dockerfile
|
37
|
+
is used as the Dockerfile. `override_dockerfile` can be set in a launch
|
38
|
+
spec via the `-D` flag to `wandb launch` or in the `overrides` section
|
39
|
+
of the launch drawer.
|
40
|
+
|
41
|
+
- If no dockerfile is set, a Dockerfile is generated from the job's
|
42
|
+
requirements and entrypoint.
|
43
|
+
"""
|
44
|
+
|
45
|
+
def __init__(self, launch_project: LaunchProject):
|
46
|
+
"""Initialize a BuildContextManager.
|
47
|
+
|
48
|
+
Arguments:
|
49
|
+
launch_project: The launch project.
|
50
|
+
"""
|
51
|
+
self._launch_project = launch_project
|
52
|
+
assert self._launch_project.project_dir is not None
|
53
|
+
self._directory = tempfile.mkdtemp()
|
54
|
+
|
55
|
+
def _generate_dockerfile(self, builder_type: str) -> str:
|
56
|
+
"""Generate a Dockerfile for the container image.
|
57
|
+
|
58
|
+
Arguments:
|
59
|
+
builder_type: The type of builder to use. One of "docker" or "kaniko".
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
The contents of the Dockerfile.
|
63
|
+
"""
|
64
|
+
launch_project = self._launch_project
|
65
|
+
entry_point = (
|
66
|
+
launch_project.override_entrypoint or launch_project.get_job_entry_point()
|
67
|
+
)
|
68
|
+
|
69
|
+
# get python versions truncated to major.minor to ensure image availability
|
70
|
+
if launch_project.python_version:
|
71
|
+
spl = launch_project.python_version.split(".")[:2]
|
72
|
+
py_version, py_major = (".".join(spl), spl[0])
|
73
|
+
else:
|
74
|
+
py_version, py_major = get_current_python_version()
|
75
|
+
|
76
|
+
python_build_image = (
|
77
|
+
f"python:{py_version}" # use full python image for package installation
|
78
|
+
)
|
79
|
+
requirements_section = get_requirements_section(
|
80
|
+
launch_project, self._directory, builder_type
|
81
|
+
)
|
82
|
+
# ----- stage 2: base -----
|
83
|
+
python_base_setup = get_base_setup(launch_project, py_version, py_major)
|
84
|
+
|
85
|
+
# set up user info
|
86
|
+
username, userid = get_docker_user(launch_project, launch_project.resource)
|
87
|
+
user_setup = get_user_setup(username, userid, launch_project.resource)
|
88
|
+
workdir = f"/home/{username}"
|
89
|
+
|
90
|
+
assert entry_point is not None
|
91
|
+
entrypoint_section = get_entrypoint_setup(entry_point)
|
92
|
+
|
93
|
+
dockerfile_contents = DOCKERFILE_TEMPLATE.format(
|
94
|
+
py_build_image=python_build_image,
|
95
|
+
requirements_section=requirements_section,
|
96
|
+
base_setup=python_base_setup,
|
97
|
+
uid=userid,
|
98
|
+
user_setup=user_setup,
|
99
|
+
workdir=workdir,
|
100
|
+
entrypoint_section=entrypoint_section,
|
101
|
+
)
|
102
|
+
return dockerfile_contents
|
103
|
+
|
104
|
+
def create_build_context(self, builder_type: str) -> Tuple[str, str]:
|
105
|
+
"""Create the build context for the container image.
|
106
|
+
|
107
|
+
Returns:
|
108
|
+
A pair of str: the path to the build context locally and the image
|
109
|
+
tag computed from the Dockerfile.
|
110
|
+
"""
|
111
|
+
entrypoint = (
|
112
|
+
self._launch_project.get_job_entry_point()
|
113
|
+
or self._launch_project.override_entrypoint
|
114
|
+
)
|
115
|
+
assert entrypoint is not None
|
116
|
+
assert entrypoint.name is not None
|
117
|
+
assert self._launch_project.project_dir is not None
|
118
|
+
|
119
|
+
# we use that as the build context.
|
120
|
+
build_context_root_dir = self._launch_project.project_dir
|
121
|
+
job_build_context = self._launch_project.job_build_context
|
122
|
+
if job_build_context:
|
123
|
+
full_path = os.path.join(build_context_root_dir, job_build_context)
|
124
|
+
if not os.path.exists(full_path):
|
125
|
+
raise LaunchError(f"Build context does not exist at {full_path}")
|
126
|
+
build_context_root_dir = full_path
|
127
|
+
|
128
|
+
# This is the case where the user specifies a Dockerfile to use.
|
129
|
+
# We use the directory containing the Dockerfile as the build context.
|
130
|
+
override_dockerfile = self._launch_project.override_dockerfile
|
131
|
+
if override_dockerfile:
|
132
|
+
full_path = os.path.join(
|
133
|
+
build_context_root_dir,
|
134
|
+
override_dockerfile,
|
135
|
+
)
|
136
|
+
if not os.path.exists(full_path):
|
137
|
+
raise LaunchError(f"Dockerfile does not exist at {full_path}")
|
138
|
+
shutil.copytree(
|
139
|
+
build_context_root_dir,
|
140
|
+
self._directory,
|
141
|
+
symlinks=True,
|
142
|
+
dirs_exist_ok=True,
|
143
|
+
ignore=shutil.ignore_patterns("fsmonitor--daemon.ipc"),
|
144
|
+
)
|
145
|
+
shutil.copy(
|
146
|
+
full_path,
|
147
|
+
os.path.join(self._directory, _WANDB_DOCKERFILE_NAME),
|
148
|
+
)
|
149
|
+
return self._directory, image_tag_from_dockerfile_and_source(
|
150
|
+
self._launch_project, open(full_path).read()
|
151
|
+
)
|
152
|
+
|
153
|
+
# If the job specifies a Dockerfile, we use that as the Dockerfile.
|
154
|
+
job_dockerfile = self._launch_project.job_dockerfile
|
155
|
+
if job_dockerfile:
|
156
|
+
dockerfile_path = os.path.join(build_context_root_dir, job_dockerfile)
|
157
|
+
if not os.path.exists(dockerfile_path):
|
158
|
+
raise LaunchError(f"Dockerfile does not exist at {dockerfile_path}")
|
159
|
+
shutil.copytree(
|
160
|
+
build_context_root_dir,
|
161
|
+
self._directory,
|
162
|
+
symlinks=True,
|
163
|
+
dirs_exist_ok=True,
|
164
|
+
ignore=shutil.ignore_patterns("fsmonitor--daemon.ipc"),
|
165
|
+
)
|
166
|
+
shutil.copy(
|
167
|
+
dockerfile_path,
|
168
|
+
os.path.join(self._directory, _WANDB_DOCKERFILE_NAME),
|
169
|
+
)
|
170
|
+
return self._directory, image_tag_from_dockerfile_and_source(
|
171
|
+
self._launch_project, open(dockerfile_path).read()
|
172
|
+
)
|
173
|
+
|
174
|
+
# This is the case where we find Dockerfile.wandb adjacent to the
|
175
|
+
# entrypoint. We use the entrypoint directory as the build context.
|
176
|
+
entrypoint_dir = os.path.dirname(entrypoint.name)
|
177
|
+
if entrypoint_dir:
|
178
|
+
path = os.path.join(
|
179
|
+
build_context_root_dir,
|
180
|
+
entrypoint_dir,
|
181
|
+
_WANDB_DOCKERFILE_NAME,
|
182
|
+
)
|
183
|
+
else:
|
184
|
+
path = os.path.join(build_context_root_dir, _WANDB_DOCKERFILE_NAME)
|
185
|
+
if os.path.exists(
|
186
|
+
path
|
187
|
+
): # We found a Dockerfile.wandb adjacent to the entrypoint.
|
188
|
+
shutil.copytree(
|
189
|
+
os.path.dirname(path),
|
190
|
+
self._directory,
|
191
|
+
symlinks=True,
|
192
|
+
dirs_exist_ok=True,
|
193
|
+
ignore=shutil.ignore_patterns("fsmonitor--daemon.ipc"),
|
194
|
+
)
|
195
|
+
# TODO: remove this once we make things more explicit for users
|
196
|
+
if entrypoint_dir:
|
197
|
+
new_path = os.path.basename(entrypoint.name)
|
198
|
+
entrypoint = self._launch_project.get_job_entry_point()
|
199
|
+
if entrypoint is not None:
|
200
|
+
entrypoint.update_entrypoint_path(new_path)
|
201
|
+
with open(path) as f:
|
202
|
+
docker_file_contents = f.read()
|
203
|
+
return self._directory, image_tag_from_dockerfile_and_source(
|
204
|
+
self._launch_project, docker_file_contents
|
205
|
+
)
|
206
|
+
|
207
|
+
# This is the case where we use our own Dockerfile template. We move
|
208
|
+
# the user code into a src directory in the build context.
|
209
|
+
dst_path = os.path.join(self._directory, "src")
|
210
|
+
assert self._launch_project.project_dir is not None
|
211
|
+
shutil.copytree(
|
212
|
+
src=self._launch_project.project_dir,
|
213
|
+
dst=dst_path,
|
214
|
+
symlinks=True,
|
215
|
+
ignore=shutil.ignore_patterns("fsmonitor--daemon.ipc"),
|
216
|
+
)
|
217
|
+
shutil.copy(
|
218
|
+
os.path.join(os.path.dirname(__file__), "templates", "_wandb_bootstrap.py"),
|
219
|
+
os.path.join(self._directory),
|
220
|
+
)
|
221
|
+
if self._launch_project.python_version:
|
222
|
+
runtime_path = os.path.join(dst_path, "runtime.txt")
|
223
|
+
with open(runtime_path, "w") as fp:
|
224
|
+
fp.write(f"python-{self._launch_project.python_version}")
|
225
|
+
|
226
|
+
# TODO: we likely don't need to pass the whole git repo into the container
|
227
|
+
# with open(os.path.join(directory, ".dockerignore"), "w") as f:
|
228
|
+
# f.write("**/.git")
|
229
|
+
with open(os.path.join(self._directory, _WANDB_DOCKERFILE_NAME), "w") as handle:
|
230
|
+
docker_file_contents = self._generate_dockerfile(builder_type=builder_type)
|
231
|
+
handle.write(docker_file_contents)
|
232
|
+
image_tag = image_tag_from_dockerfile_and_source(
|
233
|
+
self._launch_project, docker_file_contents
|
234
|
+
)
|
235
|
+
return self._directory, image_tag
|
@@ -7,8 +7,7 @@ from typing import Any, Dict, Optional
|
|
7
7
|
import wandb
|
8
8
|
import wandb.docker as docker
|
9
9
|
from wandb.sdk.launch.agent.job_status_tracker import JobAndRunStatusTracker
|
10
|
-
from wandb.sdk.launch.builder.abstract import AbstractBuilder
|
11
|
-
from wandb.sdk.launch.builder.build import registry_from_uri
|
10
|
+
from wandb.sdk.launch.builder.abstract import AbstractBuilder, registry_from_uri
|
12
11
|
from wandb.sdk.launch.environment.abstract import AbstractEnvironment
|
13
12
|
from wandb.sdk.launch.registry.abstract import AbstractRegistry
|
14
13
|
|
@@ -21,13 +20,8 @@ from ..utils import (
|
|
21
20
|
event_loop_thread_exec,
|
22
21
|
warn_failed_packages_from_build_logs,
|
23
22
|
)
|
24
|
-
from .build import
|
25
|
-
|
26
|
-
_create_docker_build_ctx,
|
27
|
-
generate_dockerfile,
|
28
|
-
image_tag_from_dockerfile_and_source,
|
29
|
-
validate_docker_installation,
|
30
|
-
)
|
23
|
+
from .build import _WANDB_DOCKERFILE_NAME, validate_docker_installation
|
24
|
+
from .context_manager import BuildContextManager
|
31
25
|
|
32
26
|
_logger = logging.getLogger(__name__)
|
33
27
|
|
@@ -41,7 +35,6 @@ class DockerBuilder(AbstractBuilder):
|
|
41
35
|
"""
|
42
36
|
|
43
37
|
builder_type = "docker"
|
44
|
-
base_image = "python:3.8"
|
45
38
|
target_platform = "linux/amd64"
|
46
39
|
|
47
40
|
def __init__(
|
@@ -124,17 +117,11 @@ class DockerBuilder(AbstractBuilder):
|
|
124
117
|
await self.verify()
|
125
118
|
await self.login()
|
126
119
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
runner_type=launch_project.resource,
|
131
|
-
builder_type="docker",
|
132
|
-
dockerfile=launch_project.override_dockerfile,
|
133
|
-
)
|
134
|
-
|
135
|
-
image_tag = image_tag_from_dockerfile_and_source(launch_project, dockerfile_str)
|
136
|
-
|
120
|
+
build_context_manager = BuildContextManager(launch_project=launch_project)
|
121
|
+
build_ctx_path, image_tag = build_context_manager.create_build_context("docker")
|
122
|
+
dockerfile = os.path.join(build_ctx_path, _WANDB_DOCKERFILE_NAME)
|
137
123
|
repository = None if not self.registry else await self.registry.get_repo_uri()
|
124
|
+
|
138
125
|
# if repo is set, use the repo name as the image name
|
139
126
|
if repository:
|
140
127
|
image_uri = f"{repository}:{image_tag}"
|
@@ -152,9 +139,6 @@ class DockerBuilder(AbstractBuilder):
|
|
152
139
|
_logger.info(
|
153
140
|
f"image {image_uri} does not already exist in repository, building."
|
154
141
|
)
|
155
|
-
|
156
|
-
build_ctx_path = _create_docker_build_ctx(launch_project, dockerfile_str)
|
157
|
-
dockerfile = os.path.join(build_ctx_path, _WANDB_DOCKERFILE_NAME)
|
158
142
|
try:
|
159
143
|
output = await event_loop_thread_exec(docker.build)(
|
160
144
|
tags=[image_uri],
|
@@ -13,8 +13,7 @@ from typing import Any, Dict, Optional
|
|
13
13
|
|
14
14
|
import wandb
|
15
15
|
from wandb.sdk.launch.agent.job_status_tracker import JobAndRunStatusTracker
|
16
|
-
from wandb.sdk.launch.builder.abstract import AbstractBuilder
|
17
|
-
from wandb.sdk.launch.builder.build import registry_from_uri
|
16
|
+
from wandb.sdk.launch.builder.abstract import AbstractBuilder, registry_from_uri
|
18
17
|
from wandb.sdk.launch.environment.abstract import AbstractEnvironment
|
19
18
|
from wandb.sdk.launch.environment.azure_environment import AzureEnvironment
|
20
19
|
from wandb.sdk.launch.registry.abstract import AbstractRegistry
|
@@ -32,12 +31,8 @@ from ..utils import (
|
|
32
31
|
get_kube_context_and_api_client,
|
33
32
|
warn_failed_packages_from_build_logs,
|
34
33
|
)
|
35
|
-
from .build import
|
36
|
-
|
37
|
-
_create_docker_build_ctx,
|
38
|
-
generate_dockerfile,
|
39
|
-
image_tag_from_dockerfile_and_source,
|
40
|
-
)
|
34
|
+
from .build import _WANDB_DOCKERFILE_NAME
|
35
|
+
from .context_manager import BuildContextManager
|
41
36
|
|
42
37
|
get_module(
|
43
38
|
"kubernetes_asyncio",
|
@@ -261,17 +256,13 @@ class KanikoBuilder(AbstractBuilder):
|
|
261
256
|
job_tracker: Optional[JobAndRunStatusTracker] = None,
|
262
257
|
) -> str:
|
263
258
|
await self.verify()
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
runner_type=launch_project.resource,
|
269
|
-
builder_type="kaniko",
|
270
|
-
dockerfile=launch_project.override_dockerfile,
|
271
|
-
)
|
272
|
-
image_tag = image_tag_from_dockerfile_and_source(launch_project, dockerfile_str)
|
259
|
+
|
260
|
+
build_contex_manager = BuildContextManager(launch_project=launch_project)
|
261
|
+
context_path, image_tag = build_contex_manager.create_build_context("kaniko")
|
262
|
+
run_id = launch_project.run_id
|
273
263
|
repo_uri = await self.registry.get_repo_uri()
|
274
264
|
image_uri = repo_uri + ":" + image_tag
|
265
|
+
|
275
266
|
if (
|
276
267
|
not launch_project.build_required()
|
277
268
|
and await self.registry.check_image_exists(image_uri)
|
@@ -279,10 +270,6 @@ class KanikoBuilder(AbstractBuilder):
|
|
279
270
|
return image_uri
|
280
271
|
|
281
272
|
_logger.info(f"Building image {image_uri}...")
|
282
|
-
|
283
|
-
context_path = _create_docker_build_ctx(launch_project, dockerfile_str)
|
284
|
-
run_id = launch_project.run_id
|
285
|
-
|
286
273
|
_, api_client = await get_kube_context_and_api_client(
|
287
274
|
kubernetes, launch_project.resource_args
|
288
275
|
)
|
@@ -492,8 +479,8 @@ class KanikoBuilder(AbstractBuilder):
|
|
492
479
|
}
|
493
480
|
)
|
494
481
|
else:
|
495
|
-
|
496
|
-
f"
|
482
|
+
wandb.termwarn(
|
483
|
+
f"{LOG_PREFIX}Automatic credential handling is not supported for registry type {type(self.registry)}. Build job: {self.build_job_name}"
|
497
484
|
)
|
498
485
|
volumes.append(
|
499
486
|
{
|
@@ -0,0 +1,92 @@
|
|
1
|
+
DOCKERFILE_TEMPLATE = """
|
2
|
+
# ----- stage 1: build -----
|
3
|
+
FROM {py_build_image} as build
|
4
|
+
|
5
|
+
# requirements section depends on pip vs conda, and presence of buildx
|
6
|
+
ENV PIP_PROGRESS_BAR off
|
7
|
+
{requirements_section}
|
8
|
+
|
9
|
+
# ----- stage 2: base -----
|
10
|
+
{base_setup}
|
11
|
+
|
12
|
+
COPY --from=build /env /env
|
13
|
+
ENV PATH="/env/bin:$PATH"
|
14
|
+
|
15
|
+
ENV SHELL /bin/bash
|
16
|
+
|
17
|
+
# some resources (eg sagemaker) must run on root
|
18
|
+
{user_setup}
|
19
|
+
|
20
|
+
WORKDIR {workdir}
|
21
|
+
RUN chown -R {uid} {workdir}
|
22
|
+
|
23
|
+
# make artifacts cache dir unrelated to build
|
24
|
+
RUN mkdir -p {workdir}/.cache && chown -R {uid} {workdir}/.cache
|
25
|
+
|
26
|
+
# copy code/etc
|
27
|
+
COPY --chown={uid} src/ {workdir}
|
28
|
+
|
29
|
+
ENV PYTHONUNBUFFERED=1
|
30
|
+
|
31
|
+
{entrypoint_section}
|
32
|
+
"""
|
33
|
+
|
34
|
+
# this goes into base_setup in TEMPLATE
|
35
|
+
PYTHON_SETUP_TEMPLATE = """
|
36
|
+
FROM {py_base_image} as base
|
37
|
+
"""
|
38
|
+
|
39
|
+
# this goes into base_setup in TEMPLATE
|
40
|
+
ACCELERATOR_SETUP_TEMPLATE = """
|
41
|
+
FROM {accelerator_base_image} as base
|
42
|
+
|
43
|
+
# make non-interactive so build doesn't block on questions
|
44
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
45
|
+
|
46
|
+
# install python
|
47
|
+
RUN apt-get update -qq && apt-get install --no-install-recommends -y \
|
48
|
+
{python_packages} \
|
49
|
+
&& apt-get -qq purge && apt-get -qq clean \
|
50
|
+
&& rm -rf /var/lib/apt/lists/*
|
51
|
+
|
52
|
+
# make sure `python` points at the right version
|
53
|
+
RUN update-alternatives --install /usr/bin/python python /usr/bin/python{py_version} 1 \
|
54
|
+
&& update-alternatives --install /usr/local/bin/python python /usr/bin/python{py_version} 1
|
55
|
+
"""
|
56
|
+
|
57
|
+
# this goes into requirements_section in TEMPLATE
|
58
|
+
PIP_TEMPLATE = """
|
59
|
+
RUN python -m venv /env
|
60
|
+
# make sure we install into the env
|
61
|
+
ENV PATH="/env/bin:$PATH"
|
62
|
+
|
63
|
+
COPY {requirements_files} ./
|
64
|
+
{buildx_optional_prefix} {pip_install}
|
65
|
+
"""
|
66
|
+
|
67
|
+
# this goes into requirements_section in TEMPLATE
|
68
|
+
CONDA_TEMPLATE = """
|
69
|
+
COPY src/environment.yml .
|
70
|
+
{buildx_optional_prefix} conda env create -f environment.yml -n env
|
71
|
+
|
72
|
+
# pack the environment so that we can transfer to the base image
|
73
|
+
RUN conda install -c conda-forge conda-pack
|
74
|
+
RUN conda pack -n env -o /tmp/env.tar && \
|
75
|
+
mkdir /env && cd /env && tar xf /tmp/env.tar && \
|
76
|
+
rm /tmp/env.tar
|
77
|
+
RUN /env/bin/conda-unpack
|
78
|
+
"""
|
79
|
+
|
80
|
+
USER_CREATE_TEMPLATE = """
|
81
|
+
RUN useradd \
|
82
|
+
--create-home \
|
83
|
+
--no-log-init \
|
84
|
+
--shell /bin/bash \
|
85
|
+
--gid 0 \
|
86
|
+
--uid {uid} \
|
87
|
+
{user} || echo ""
|
88
|
+
"""
|
89
|
+
|
90
|
+
ENTRYPOINT_TEMPLATE = """
|
91
|
+
ENTRYPOINT {entrypoint}
|
92
|
+
"""
|
wandb/sdk/launch/create_job.py
CHANGED
@@ -10,9 +10,12 @@ import wandb
|
|
10
10
|
from wandb.apis.internal import Api
|
11
11
|
from wandb.sdk.artifacts.artifact import Artifact
|
12
12
|
from wandb.sdk.internal.job_builder import JobBuilder
|
13
|
-
from wandb.sdk.launch.builder.build import get_current_python_version
|
14
13
|
from wandb.sdk.launch.git_reference import GitReference
|
15
|
-
from wandb.sdk.launch.utils import
|
14
|
+
from wandb.sdk.launch.utils import (
|
15
|
+
_is_git_uri,
|
16
|
+
get_current_python_version,
|
17
|
+
get_entrypoint_file,
|
18
|
+
)
|
16
19
|
from wandb.sdk.lib import filesystem
|
17
20
|
from wandb.util import make_artifact_name_safe
|
18
21
|
|
@@ -34,6 +37,8 @@ def create_job(
|
|
34
37
|
runtime: Optional[str] = None,
|
35
38
|
entrypoint: Optional[str] = None,
|
36
39
|
git_hash: Optional[str] = None,
|
40
|
+
build_context: Optional[str] = None,
|
41
|
+
dockerfile: Optional[str] = None,
|
37
42
|
) -> Optional[Artifact]:
|
38
43
|
"""Create a job from a path, not as the output of a run.
|
39
44
|
|
@@ -46,9 +51,12 @@ def create_job(
|
|
46
51
|
description (Optional[str]): Description of the job.
|
47
52
|
aliases (Optional[List[str]]): Aliases for the job.
|
48
53
|
runtime (Optional[str]): Python runtime of the job, like 3.9.
|
49
|
-
entrypoint (Optional[str]): Entrypoint of the job.
|
54
|
+
entrypoint (Optional[str]): Entrypoint of the job. If build_context is
|
55
|
+
provided, path is relative to build_context.
|
50
56
|
git_hash (Optional[str]): Git hash of a specific commit, when using git type jobs.
|
51
|
-
|
57
|
+
build_context (Optional[str]): Path to the build context, when using image type jobs.
|
58
|
+
dockerfile (Optional[str]): Path to the Dockerfile, when using image type jobs.
|
59
|
+
If build_context is provided, path is relative to build_context.
|
52
60
|
|
53
61
|
Returns:
|
54
62
|
Optional[Artifact]: The artifact created by the job, the action (for printing), and job aliases.
|
@@ -85,6 +93,8 @@ def create_job(
|
|
85
93
|
runtime,
|
86
94
|
entrypoint,
|
87
95
|
git_hash,
|
96
|
+
build_context,
|
97
|
+
dockerfile,
|
88
98
|
)
|
89
99
|
|
90
100
|
return artifact_job
|
@@ -102,6 +112,8 @@ def _create_job(
|
|
102
112
|
runtime: Optional[str] = None,
|
103
113
|
entrypoint: Optional[str] = None,
|
104
114
|
git_hash: Optional[str] = None,
|
115
|
+
build_context: Optional[str] = None,
|
116
|
+
dockerfile: Optional[str] = None,
|
105
117
|
) -> Tuple[Optional[Artifact], str, List[str]]:
|
106
118
|
wandb.termlog(f"Creating launch job of type: {job_type}...")
|
107
119
|
|
@@ -172,7 +184,11 @@ def _create_job(
|
|
172
184
|
name = job_name
|
173
185
|
|
174
186
|
# build job artifact, loads wandb-metadata and creates wandb-job.json here
|
175
|
-
artifact = job_builder.build(
|
187
|
+
artifact = job_builder.build(
|
188
|
+
api.api,
|
189
|
+
dockerfile=dockerfile,
|
190
|
+
build_context=build_context,
|
191
|
+
)
|
176
192
|
if not artifact:
|
177
193
|
wandb.termerror("JobBuilder failed to build a job")
|
178
194
|
_logger.debug("Failed to build job, check job source and metadata")
|
@@ -195,7 +211,7 @@ def _create_job(
|
|
195
211
|
project_name=project,
|
196
212
|
run_name=run.id, # type: ignore # run will be deleted after creation
|
197
213
|
description=description,
|
198
|
-
metadata=
|
214
|
+
metadata={"_partial": True},
|
199
215
|
is_user_created=True,
|
200
216
|
aliases=[{"artifactCollectionName": name, "alias": a} for a in aliases],
|
201
217
|
)
|
@@ -229,8 +245,9 @@ def _make_metadata_for_partial_job(
|
|
229
245
|
entrypoint: Optional[str],
|
230
246
|
) -> Tuple[Optional[Dict[str, Any]], Optional[List[str]]]:
|
231
247
|
"""Create metadata for partial jobs, return metadata and requirements."""
|
232
|
-
metadata = {
|
248
|
+
metadata = {}
|
233
249
|
if job_type == "git":
|
250
|
+
assert entrypoint is not None
|
234
251
|
repo_metadata = _create_repo_metadata(
|
235
252
|
path=path,
|
236
253
|
tempdir=tempdir.name,
|
@@ -245,12 +262,7 @@ def _make_metadata_for_partial_job(
|
|
245
262
|
return metadata, None
|
246
263
|
|
247
264
|
if job_type == "code":
|
248
|
-
|
249
|
-
wandb.termerror(
|
250
|
-
"Artifact jobs must have an entrypoint, either included in the path or specified with -E"
|
251
|
-
)
|
252
|
-
return None, None
|
253
|
-
|
265
|
+
assert entrypoint is not None
|
254
266
|
artifact_metadata, requirements = _create_artifact_metadata(
|
255
267
|
path=path, entrypoint=entrypoint, runtime=runtime
|
256
268
|
)
|
@@ -276,10 +288,18 @@ def _make_metadata_for_partial_job(
|
|
276
288
|
return None, None
|
277
289
|
|
278
290
|
|
291
|
+
def _maybe_warn_python_no_executable(entrypoint: str):
|
292
|
+
entrypoint_list = entrypoint.split(" ")
|
293
|
+
if len(entrypoint_list) == 1 and entrypoint_list[0].endswith(".py"):
|
294
|
+
wandb.termwarn(
|
295
|
+
f"Entrypoint {entrypoint} is a python file without an executable, you may want to use `python {entrypoint}` as the entrypoint instead."
|
296
|
+
)
|
297
|
+
|
298
|
+
|
279
299
|
def _create_repo_metadata(
|
280
300
|
path: str,
|
281
301
|
tempdir: str,
|
282
|
-
entrypoint:
|
302
|
+
entrypoint: str,
|
283
303
|
git_hash: Optional[str] = None,
|
284
304
|
runtime: Optional[str] = None,
|
285
305
|
) -> Optional[Dict[str, Any]]:
|
@@ -287,6 +307,9 @@ def _create_repo_metadata(
|
|
287
307
|
if entrypoint and ".." in entrypoint:
|
288
308
|
wandb.termerror("Entrypoint cannot contain backward path traversal")
|
289
309
|
return None
|
310
|
+
|
311
|
+
_maybe_warn_python_no_executable(entrypoint)
|
312
|
+
|
290
313
|
if not _is_git_uri(path):
|
291
314
|
wandb.termerror("Path must be a git URI")
|
292
315
|
return None
|
@@ -315,32 +338,16 @@ def _create_repo_metadata(
|
|
315
338
|
with open(os.path.join(local_dir, ".python-version")) as f:
|
316
339
|
python_version = f.read().strip().splitlines()[0]
|
317
340
|
else:
|
318
|
-
|
341
|
+
python_version, _ = get_current_python_version()
|
319
342
|
|
320
343
|
python_version = _clean_python_version(python_version)
|
321
344
|
|
322
|
-
# check if entrypoint is valid
|
323
|
-
assert entrypoint is not None
|
324
|
-
entrypoint_list = entrypoint.split(" ")
|
325
|
-
entrypoint_file = get_entrypoint_file(entrypoint_list)
|
326
|
-
if not entrypoint_file:
|
327
|
-
wandb.termerror(
|
328
|
-
f"Entrypoint {entrypoint} is invalid. An entrypoint should include both an executable and a file, for example 'python train.py'"
|
329
|
-
)
|
330
|
-
return None
|
331
|
-
|
332
|
-
if not os.path.exists(os.path.join(local_dir, entrypoint_file)):
|
333
|
-
wandb.termerror(f"Entrypoint file {entrypoint_file} not found in git repo")
|
334
|
-
return None
|
335
|
-
|
336
345
|
metadata = {
|
337
346
|
"git": {
|
338
347
|
"commit": commit,
|
339
348
|
"remote": ref.url,
|
340
349
|
},
|
341
|
-
"
|
342
|
-
"codePath": entrypoint_file,
|
343
|
-
"entrypoint": entrypoint_list,
|
350
|
+
"entrypoint": entrypoint.split(" "),
|
344
351
|
"python": python_version, # used to build container
|
345
352
|
"notebook": False, # partial jobs from notebooks not supported
|
346
353
|
}
|
@@ -354,13 +361,11 @@ def _create_artifact_metadata(
|
|
354
361
|
if not os.path.isdir(path):
|
355
362
|
wandb.termerror("Path must be a valid file or directory")
|
356
363
|
return {}, []
|
364
|
+
|
365
|
+
_maybe_warn_python_no_executable(entrypoint)
|
366
|
+
|
357
367
|
entrypoint_list = entrypoint.split(" ")
|
358
368
|
entrypoint_file = get_entrypoint_file(entrypoint_list)
|
359
|
-
if not entrypoint_file:
|
360
|
-
wandb.termerror(
|
361
|
-
f"Entrypoint {entrypoint} is invalid. An entrypoint should include both an executable and a file, for example 'python train.py'"
|
362
|
-
)
|
363
|
-
return None, None
|
364
369
|
|
365
370
|
# read local requirements.txt and dump to temp dir for builder
|
366
371
|
requirements = []
|
@@ -369,6 +374,9 @@ def _create_artifact_metadata(
|
|
369
374
|
with open(depspath) as f:
|
370
375
|
requirements = f.read().splitlines()
|
371
376
|
|
377
|
+
if not any(["wandb" in r for r in requirements]):
|
378
|
+
wandb.termwarn("wandb is not present in requirements.txt.")
|
379
|
+
|
372
380
|
if runtime:
|
373
381
|
python_version = _clean_python_version(runtime)
|
374
382
|
else:
|
@@ -399,6 +407,7 @@ def _configure_job_builder_for_partial(tmpdir: str, job_source: str) -> JobBuild
|
|
399
407
|
settings=settings, # type: ignore
|
400
408
|
verbose=True,
|
401
409
|
)
|
410
|
+
job_builder._partial = True
|
402
411
|
# never allow notebook runs
|
403
412
|
job_builder._is_notebook_run = False
|
404
413
|
# set run inputs and outputs to empty dicts
|
@@ -421,15 +430,12 @@ def _make_code_artifact(
|
|
421
430
|
|
422
431
|
Returns the name of the eventual job.
|
423
432
|
"""
|
424
|
-
assert entrypoint is not None
|
425
433
|
entrypoint_list = entrypoint.split(" ")
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
return None
|
432
|
-
|
434
|
+
# We no longer require the entrypoint to end in an existing file. But we
|
435
|
+
# need something to use as the default job artifact name. In the future we
|
436
|
+
# may require the user to provide a job name explicitly when calling
|
437
|
+
# wandb job create.
|
438
|
+
entrypoint_file = entrypoint_list[-1]
|
433
439
|
artifact_name = _make_code_artifact_name(os.path.join(path, entrypoint_file), name)
|
434
440
|
code_artifact = wandb.Artifact(
|
435
441
|
name=artifact_name,
|