wandb 0.22.0__py3-none-macosx_12_0_arm64.whl → 0.22.2__py3-none-macosx_12_0_arm64.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.
- wandb/__init__.py +1 -1
- wandb/__init__.pyi +8 -5
- wandb/_pydantic/__init__.py +12 -11
- wandb/_pydantic/base.py +49 -19
- wandb/apis/__init__.py +2 -0
- wandb/apis/attrs.py +2 -0
- wandb/apis/importers/internals/internal.py +16 -23
- wandb/apis/internal.py +2 -0
- wandb/apis/normalize.py +2 -0
- wandb/apis/public/__init__.py +3 -2
- wandb/apis/public/api.py +215 -164
- wandb/apis/public/artifacts.py +23 -20
- wandb/apis/public/const.py +2 -0
- wandb/apis/public/files.py +33 -24
- wandb/apis/public/history.py +2 -0
- wandb/apis/public/jobs.py +20 -18
- wandb/apis/public/projects.py +4 -2
- wandb/apis/public/query_generator.py +3 -0
- wandb/apis/public/registries/__init__.py +7 -0
- wandb/apis/public/registries/_freezable_list.py +9 -12
- wandb/apis/public/registries/registries_search.py +8 -6
- wandb/apis/public/registries/registry.py +22 -17
- wandb/apis/public/reports.py +2 -0
- wandb/apis/public/runs.py +261 -57
- wandb/apis/public/sweeps.py +10 -9
- wandb/apis/public/teams.py +2 -0
- wandb/apis/public/users.py +2 -0
- wandb/apis/public/utils.py +16 -15
- wandb/automations/_generated/__init__.py +54 -127
- wandb/automations/_generated/create_generic_webhook_integration.py +1 -7
- wandb/automations/_generated/fragments.py +26 -91
- wandb/bin/gpu_stats +0 -0
- wandb/bin/wandb-core +0 -0
- wandb/cli/beta.py +16 -2
- wandb/cli/beta_leet.py +74 -0
- wandb/cli/beta_sync.py +9 -11
- wandb/cli/cli.py +34 -7
- wandb/errors/errors.py +3 -3
- wandb/proto/v3/wandb_api_pb2.py +86 -0
- wandb/proto/v3/wandb_internal_pb2.py +352 -351
- wandb/proto/v3/wandb_settings_pb2.py +2 -2
- wandb/proto/v3/wandb_sync_pb2.py +19 -6
- wandb/proto/v4/wandb_api_pb2.py +37 -0
- wandb/proto/v4/wandb_internal_pb2.py +352 -351
- wandb/proto/v4/wandb_settings_pb2.py +2 -2
- wandb/proto/v4/wandb_sync_pb2.py +10 -6
- wandb/proto/v5/wandb_api_pb2.py +38 -0
- wandb/proto/v5/wandb_internal_pb2.py +352 -351
- wandb/proto/v5/wandb_settings_pb2.py +2 -2
- wandb/proto/v5/wandb_sync_pb2.py +10 -6
- wandb/proto/v6/wandb_api_pb2.py +48 -0
- wandb/proto/v6/wandb_internal_pb2.py +352 -351
- wandb/proto/v6/wandb_settings_pb2.py +2 -2
- wandb/proto/v6/wandb_sync_pb2.py +10 -6
- wandb/proto/wandb_api_pb2.py +18 -0
- wandb/proto/wandb_generate_proto.py +1 -0
- wandb/sdk/artifacts/_factories.py +7 -2
- wandb/sdk/artifacts/_generated/__init__.py +112 -412
- wandb/sdk/artifacts/_generated/fragments.py +65 -0
- wandb/sdk/artifacts/_generated/operations.py +52 -22
- wandb/sdk/artifacts/_generated/run_input_artifacts.py +3 -23
- wandb/sdk/artifacts/_generated/run_output_artifacts.py +3 -23
- wandb/sdk/artifacts/_generated/type_info.py +19 -0
- wandb/sdk/artifacts/_gqlutils.py +47 -0
- wandb/sdk/artifacts/_models/__init__.py +4 -0
- wandb/sdk/artifacts/_models/base_model.py +20 -0
- wandb/sdk/artifacts/_validators.py +40 -12
- wandb/sdk/artifacts/artifact.py +99 -118
- wandb/sdk/artifacts/artifact_file_cache.py +6 -1
- wandb/sdk/artifacts/artifact_manifest_entry.py +67 -14
- wandb/sdk/artifacts/storage_handler.py +18 -12
- wandb/sdk/artifacts/storage_handlers/azure_handler.py +11 -6
- wandb/sdk/artifacts/storage_handlers/gcs_handler.py +9 -6
- wandb/sdk/artifacts/storage_handlers/http_handler.py +9 -4
- wandb/sdk/artifacts/storage_handlers/local_file_handler.py +10 -6
- wandb/sdk/artifacts/storage_handlers/multi_handler.py +5 -4
- wandb/sdk/artifacts/storage_handlers/s3_handler.py +10 -8
- wandb/sdk/artifacts/storage_handlers/tracking_handler.py +6 -4
- wandb/sdk/artifacts/storage_handlers/wb_artifact_handler.py +24 -21
- wandb/sdk/artifacts/storage_handlers/wb_local_artifact_handler.py +4 -2
- wandb/sdk/artifacts/storage_policies/_multipart.py +187 -0
- wandb/sdk/artifacts/storage_policies/wandb_storage_policy.py +71 -242
- wandb/sdk/artifacts/storage_policy.py +25 -12
- wandb/sdk/data_types/bokeh.py +5 -1
- wandb/sdk/data_types/image.py +17 -6
- wandb/sdk/data_types/object_3d.py +67 -2
- wandb/sdk/interface/interface.py +31 -4
- wandb/sdk/interface/interface_queue.py +10 -0
- wandb/sdk/interface/interface_shared.py +0 -7
- wandb/sdk/interface/interface_sock.py +9 -3
- wandb/sdk/internal/_generated/__init__.py +2 -12
- wandb/sdk/internal/job_builder.py +27 -10
- wandb/sdk/internal/sender.py +5 -2
- wandb/sdk/internal/settings_static.py +2 -82
- wandb/sdk/launch/create_job.py +2 -1
- wandb/sdk/launch/runner/kubernetes_runner.py +25 -20
- wandb/sdk/launch/utils.py +82 -1
- wandb/sdk/lib/progress.py +8 -74
- wandb/sdk/lib/service/service_client.py +5 -9
- wandb/sdk/lib/service/service_connection.py +39 -23
- wandb/sdk/mailbox/mailbox_handle.py +2 -0
- wandb/sdk/projects/_generated/__init__.py +12 -33
- wandb/sdk/wandb_init.py +23 -3
- wandb/sdk/wandb_login.py +53 -27
- wandb/sdk/wandb_run.py +10 -5
- wandb/sdk/wandb_settings.py +63 -25
- wandb/sync/sync.py +7 -2
- wandb/util.py +1 -1
- {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/METADATA +1 -1
- {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/RECORD +113 -103
- wandb/sdk/artifacts/_graphql_fragments.py +0 -19
- {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/WHEEL +0 -0
- {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/entry_points.txt +0 -0
- {wandb-0.22.0.dist-info → wandb-0.22.2.dist-info}/licenses/LICENSE +0 -0
@@ -138,7 +138,7 @@ def box3d(
|
|
138
138
|
label: "Optional[str]" = None,
|
139
139
|
score: "Optional[numeric]" = None,
|
140
140
|
) -> "Box3D":
|
141
|
-
"""
|
141
|
+
"""A 3D bounding box. The box is specified by its center, size and orientation.
|
142
142
|
|
143
143
|
Args:
|
144
144
|
center: The center point of the box as a length-3 ndarray.
|
@@ -149,7 +149,72 @@ def box3d(
|
|
149
149
|
r + xi + yj + zk.
|
150
150
|
color: The box's color as an (r, g, b) tuple with 0 <= r,g,b <= 1.
|
151
151
|
label: An optional label for the box.
|
152
|
-
score: An optional score for the box.
|
152
|
+
score: An optional score for the box. Typically used to indicate
|
153
|
+
the confidence of a detection.
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
A Box3D object.
|
157
|
+
|
158
|
+
Example:
|
159
|
+
The following example creates a point cloud with 60 boxes rotating
|
160
|
+
around the X, Y and Z axes.
|
161
|
+
|
162
|
+
```python
|
163
|
+
import wandb
|
164
|
+
|
165
|
+
import math
|
166
|
+
import numpy as np
|
167
|
+
from scipy.spatial.transform import Rotation
|
168
|
+
|
169
|
+
|
170
|
+
with wandb.init() as run:
|
171
|
+
run.log(
|
172
|
+
{
|
173
|
+
"points": wandb.Object3D.from_point_cloud(
|
174
|
+
points=np.random.uniform(-5, 5, size=(100, 3)),
|
175
|
+
boxes=[
|
176
|
+
wandb.box3d(
|
177
|
+
center=(0.3 * t - 3, 0, 0),
|
178
|
+
size=(0.1, 0.1, 0.1),
|
179
|
+
orientation=Rotation.from_euler(
|
180
|
+
"xyz", [t * math.pi / 10, 0, 0]
|
181
|
+
).as_quat(),
|
182
|
+
color=(0.5 + t / 40, 0.5, 0.5),
|
183
|
+
label=f"box {t}",
|
184
|
+
score=0.9,
|
185
|
+
)
|
186
|
+
for t in range(20)
|
187
|
+
]
|
188
|
+
+ [
|
189
|
+
wandb.box3d(
|
190
|
+
center=(0, 0.3 * t - 3, 0.3),
|
191
|
+
size=(0.1, 0.1, 0.1),
|
192
|
+
orientation=Rotation.from_euler(
|
193
|
+
"xyz", [0, t * math.pi / 10, 0]
|
194
|
+
).as_quat(),
|
195
|
+
color=(0.5, 0.5 + t / 40, 0.5),
|
196
|
+
label=f"box {t}",
|
197
|
+
score=0.9,
|
198
|
+
)
|
199
|
+
for t in range(20)
|
200
|
+
]
|
201
|
+
+ [
|
202
|
+
wandb.box3d(
|
203
|
+
center=(0.3, 0.3, 0.3 * t - 3),
|
204
|
+
size=(0.1, 0.1, 0.1),
|
205
|
+
orientation=Rotation.from_euler(
|
206
|
+
"xyz", [0, 0, t * math.pi / 10]
|
207
|
+
).as_quat(),
|
208
|
+
color=(0.5, 0.5, 0.5 + t / 40),
|
209
|
+
label=f"box {t}",
|
210
|
+
score=0.9,
|
211
|
+
)
|
212
|
+
for t in range(20)
|
213
|
+
],
|
214
|
+
),
|
215
|
+
}
|
216
|
+
)
|
217
|
+
```
|
153
218
|
"""
|
154
219
|
try:
|
155
220
|
import numpy as np
|
wandb/sdk/interface/interface.py
CHANGED
@@ -87,11 +87,38 @@ def file_enum_to_policy(enum: "pb.FilesItem.PolicyType.V") -> "PolicyName":
|
|
87
87
|
|
88
88
|
|
89
89
|
class InterfaceBase:
|
90
|
+
"""Methods for sending different types of Records to the service.
|
91
|
+
|
92
|
+
None of the methods may be called from an asyncio context other than
|
93
|
+
deliver_async().
|
94
|
+
"""
|
95
|
+
|
90
96
|
_drop: bool
|
91
97
|
|
92
98
|
def __init__(self) -> None:
|
93
99
|
self._drop = False
|
94
100
|
|
101
|
+
@abstractmethod
|
102
|
+
async def deliver_async(
|
103
|
+
self,
|
104
|
+
record: pb.Record,
|
105
|
+
) -> MailboxHandle[pb.Result]:
|
106
|
+
"""Send a record and create a handle to wait for the response.
|
107
|
+
|
108
|
+
The synchronous publish and deliver methods on this class cannot be
|
109
|
+
called in the asyncio thread because they block. Instead of having
|
110
|
+
an async copy of every method, this is a general method for sending
|
111
|
+
any kind of record in the asyncio thread.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
record: The record to send. This method takes ownership of the
|
115
|
+
record and it must not be used afterward.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
A handle to wait for a response to the record.
|
119
|
+
"""
|
120
|
+
raise NotImplementedError
|
121
|
+
|
95
122
|
def publish_header(self) -> None:
|
96
123
|
header = pb.HeaderRecord()
|
97
124
|
self._publish_header(header)
|
@@ -392,9 +419,13 @@ class InterfaceBase:
|
|
392
419
|
proto_manifest.manifest_file_path = path
|
393
420
|
return proto_manifest
|
394
421
|
|
422
|
+
# Set storage policy on storageLayout (always V2) and storageRegion, only allow coreweave-us on wandb.ai for now.
|
423
|
+
# NOTE: the decode logic is NewManifestFromProto in core/pkg/artifacts/manifest.go
|
424
|
+
# The creation logic is in artifacts/_factories.py make_storage_policy
|
395
425
|
for k, v in artifact_manifest.storage_policy.config().items() or {}.items():
|
396
426
|
cfg = proto_manifest.storage_policy_config.add()
|
397
427
|
cfg.key = k
|
428
|
+
# TODO: Why json.dumps when existing values are plain string? We want to send complex structure without defining the proto?
|
398
429
|
cfg.value_json = json.dumps(v)
|
399
430
|
|
400
431
|
for entry in sorted(artifact_manifest.entries.values(), key=lambda k: k.path):
|
@@ -1020,10 +1051,6 @@ class InterfaceBase:
|
|
1020
1051
|
) -> MailboxHandle[pb.Result]:
|
1021
1052
|
raise NotImplementedError
|
1022
1053
|
|
1023
|
-
@abstractmethod
|
1024
|
-
def deliver_operation_stats(self) -> MailboxHandle[pb.Result]:
|
1025
|
-
raise NotImplementedError
|
1026
|
-
|
1027
1054
|
def deliver_poll_exit(self) -> MailboxHandle[pb.Result]:
|
1028
1055
|
poll_exit = pb.PollExitRequest()
|
1029
1056
|
return self._deliver_poll_exit(poll_exit)
|
@@ -8,12 +8,15 @@ import logging
|
|
8
8
|
from multiprocessing.process import BaseProcess
|
9
9
|
from typing import TYPE_CHECKING, Optional
|
10
10
|
|
11
|
+
from typing_extensions import override
|
12
|
+
|
11
13
|
from .interface_shared import InterfaceShared
|
12
14
|
|
13
15
|
if TYPE_CHECKING:
|
14
16
|
from queue import Queue
|
15
17
|
|
16
18
|
from wandb.proto import wandb_internal_pb2 as pb
|
19
|
+
from wandb.sdk.mailbox.mailbox_handle import MailboxHandle
|
17
20
|
|
18
21
|
|
19
22
|
logger = logging.getLogger("wandb")
|
@@ -31,6 +34,13 @@ class InterfaceQueue(InterfaceShared):
|
|
31
34
|
self._process = process
|
32
35
|
super().__init__()
|
33
36
|
|
37
|
+
@override
|
38
|
+
async def deliver_async(
|
39
|
+
self,
|
40
|
+
record: "pb.Record",
|
41
|
+
) -> "MailboxHandle[pb.Result]":
|
42
|
+
raise NotImplementedError
|
43
|
+
|
34
44
|
def _publish(self, record: "pb.Record", local: Optional[bool] = None) -> None:
|
35
45
|
if self._process and not self._process.is_alive():
|
36
46
|
raise Exception("The wandb backend process has shutdown")
|
@@ -87,7 +87,6 @@ class InterfaceShared(InterfaceBase):
|
|
87
87
|
stop_status: Optional[pb.StopStatusRequest] = None,
|
88
88
|
internal_messages: Optional[pb.InternalMessagesRequest] = None,
|
89
89
|
network_status: Optional[pb.NetworkStatusRequest] = None,
|
90
|
-
operation_stats: Optional[pb.OperationStatsRequest] = None,
|
91
90
|
poll_exit: Optional[pb.PollExitRequest] = None,
|
92
91
|
partial_history: Optional[pb.PartialHistoryRequest] = None,
|
93
92
|
sampled_history: Optional[pb.SampledHistoryRequest] = None,
|
@@ -129,8 +128,6 @@ class InterfaceShared(InterfaceBase):
|
|
129
128
|
request.internal_messages.CopyFrom(internal_messages)
|
130
129
|
elif network_status:
|
131
130
|
request.network_status.CopyFrom(network_status)
|
132
|
-
elif operation_stats:
|
133
|
-
request.operations.CopyFrom(operation_stats)
|
134
131
|
elif poll_exit:
|
135
132
|
request.poll_exit.CopyFrom(poll_exit)
|
136
133
|
elif partial_history:
|
@@ -424,10 +421,6 @@ class InterfaceShared(InterfaceBase):
|
|
424
421
|
record = self._make_record(exit=exit_data)
|
425
422
|
return self._deliver(record)
|
426
423
|
|
427
|
-
def deliver_operation_stats(self):
|
428
|
-
record = self._make_request(operation_stats=pb.OperationStatsRequest())
|
429
|
-
return self._deliver(record)
|
430
|
-
|
431
424
|
def _deliver_poll_exit(
|
432
425
|
self,
|
433
426
|
poll_exit: pb.PollExitRequest,
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any
|
|
6
6
|
from typing_extensions import override
|
7
7
|
|
8
8
|
from wandb.proto import wandb_server_pb2 as spb
|
9
|
+
from wandb.sdk.lib import asyncio_manager
|
9
10
|
|
10
11
|
from .interface_shared import InterfaceShared
|
11
12
|
|
@@ -21,10 +22,12 @@ logger = logging.getLogger("wandb")
|
|
21
22
|
class InterfaceSock(InterfaceShared):
|
22
23
|
def __init__(
|
23
24
|
self,
|
25
|
+
asyncer: asyncio_manager.AsyncioManager,
|
24
26
|
client: ServiceClient,
|
25
27
|
stream_id: str,
|
26
28
|
) -> None:
|
27
29
|
super().__init__()
|
30
|
+
self._asyncer = asyncer
|
28
31
|
self._client = client
|
29
32
|
self._stream_id = stream_id
|
30
33
|
|
@@ -37,13 +40,16 @@ class InterfaceSock(InterfaceShared):
|
|
37
40
|
self._assign(record)
|
38
41
|
request = spb.ServerRequest()
|
39
42
|
request.record_publish.CopyFrom(record)
|
40
|
-
self._client.publish(request)
|
43
|
+
self._asyncer.run(lambda: self._client.publish(request))
|
41
44
|
|
42
|
-
@override
|
43
45
|
def _deliver(self, record: pb.Record) -> MailboxHandle[pb.Result]:
|
46
|
+
return self._asyncer.run(lambda: self.deliver_async(record))
|
47
|
+
|
48
|
+
@override
|
49
|
+
async def deliver_async(self, record: pb.Record) -> MailboxHandle[pb.Result]:
|
44
50
|
self._assign(record)
|
45
51
|
request = spb.ServerRequest()
|
46
52
|
request.record_publish.CopyFrom(record)
|
47
53
|
|
48
|
-
handle = self._client.deliver(request)
|
54
|
+
handle = await self._client.deliver(request)
|
49
55
|
return handle.map(lambda response: response.result_communicate)
|
@@ -1,15 +1,5 @@
|
|
1
1
|
# Generated by ariadne-codegen
|
2
2
|
|
3
|
+
__all__ = ["SERVER_FEATURES_QUERY_GQL", "ServerFeaturesQuery"]
|
3
4
|
from .operations import SERVER_FEATURES_QUERY_GQL
|
4
|
-
from .server_features_query import
|
5
|
-
ServerFeaturesQuery,
|
6
|
-
ServerFeaturesQueryServerInfo,
|
7
|
-
ServerFeaturesQueryServerInfoFeatures,
|
8
|
-
)
|
9
|
-
|
10
|
-
__all__ = [
|
11
|
-
"SERVER_FEATURES_QUERY_GQL",
|
12
|
-
"ServerFeaturesQuery",
|
13
|
-
"ServerFeaturesQueryServerInfo",
|
14
|
-
"ServerFeaturesQueryServerInfoFeatures",
|
15
|
-
]
|
5
|
+
from .server_features_query import ServerFeaturesQuery
|
@@ -132,6 +132,7 @@ def get_min_supported_for_source_dict(
|
|
132
132
|
|
133
133
|
class JobBuilder:
|
134
134
|
_settings: SettingsStatic
|
135
|
+
_files_dir: str
|
135
136
|
_metadatafile_path: Optional[str]
|
136
137
|
_requirements_path: Optional[str]
|
137
138
|
_config: Optional[Dict[str, Any]]
|
@@ -146,8 +147,26 @@ class JobBuilder:
|
|
146
147
|
_verbose: bool
|
147
148
|
_services: Dict[str, str]
|
148
149
|
|
149
|
-
def __init__(
|
150
|
+
def __init__(
|
151
|
+
self,
|
152
|
+
settings: SettingsStatic,
|
153
|
+
verbose: bool = False,
|
154
|
+
*,
|
155
|
+
files_dir: str,
|
156
|
+
):
|
157
|
+
"""Instantiate a JobBuilder.
|
158
|
+
|
159
|
+
Args:
|
160
|
+
settings: Parameters for the job builder.
|
161
|
+
In a run, this is the run's settings.
|
162
|
+
Otherwise, this is a set of undocumented parameters,
|
163
|
+
all of which should be made explicit like files_dir.
|
164
|
+
files_dir: The directory where to write files.
|
165
|
+
In a run, this should be the run's files directory.
|
166
|
+
"""
|
150
167
|
self._settings = settings
|
168
|
+
self._files_dir = files_dir
|
169
|
+
|
151
170
|
self._metadatafile_path = None
|
152
171
|
self._requirements_path = None
|
153
172
|
self._config = None
|
@@ -460,9 +479,7 @@ class JobBuilder:
|
|
460
479
|
)
|
461
480
|
return None
|
462
481
|
|
463
|
-
if not os.path.exists(
|
464
|
-
os.path.join(self._settings.files_dir, REQUIREMENTS_FNAME)
|
465
|
-
):
|
482
|
+
if not os.path.exists(os.path.join(self._files_dir, REQUIREMENTS_FNAME)):
|
466
483
|
self._log_if_verbose(
|
467
484
|
"No requirements.txt found, not creating job artifact. See https://docs.wandb.ai/guides/launch/create-job",
|
468
485
|
"warn",
|
@@ -471,7 +488,7 @@ class JobBuilder:
|
|
471
488
|
metadata = self._handle_metadata_file()
|
472
489
|
if metadata is None:
|
473
490
|
self._log_if_verbose(
|
474
|
-
f"Ensure read and write access to run files dir: {self.
|
491
|
+
f"Ensure read and write access to run files dir: {self._files_dir}, control this via the WANDB_DIR env var. See https://docs.wandb.ai/guides/track/environment-variables",
|
475
492
|
"warn",
|
476
493
|
)
|
477
494
|
return None
|
@@ -560,15 +577,15 @@ class JobBuilder:
|
|
560
577
|
f.write(json.dumps(source_info, indent=4))
|
561
578
|
|
562
579
|
artifact.add_file(
|
563
|
-
os.path.join(self.
|
580
|
+
os.path.join(self._files_dir, REQUIREMENTS_FNAME),
|
564
581
|
name=FROZEN_REQUIREMENTS_FNAME,
|
565
582
|
)
|
566
583
|
|
567
584
|
if source_type == "repo":
|
568
585
|
# add diff
|
569
|
-
if os.path.exists(os.path.join(self.
|
586
|
+
if os.path.exists(os.path.join(self._files_dir, DIFF_FNAME)):
|
570
587
|
artifact.add_file(
|
571
|
-
os.path.join(self.
|
588
|
+
os.path.join(self._files_dir, DIFF_FNAME),
|
572
589
|
name=DIFF_FNAME,
|
573
590
|
)
|
574
591
|
|
@@ -619,8 +636,8 @@ class JobBuilder:
|
|
619
636
|
def _handle_metadata_file(
|
620
637
|
self,
|
621
638
|
) -> Optional[Dict]:
|
622
|
-
if os.path.exists(os.path.join(self.
|
623
|
-
with open(os.path.join(self.
|
639
|
+
if os.path.exists(os.path.join(self._files_dir, METADATA_FNAME)):
|
640
|
+
with open(os.path.join(self._files_dir, METADATA_FNAME)) as f:
|
624
641
|
metadata: Dict = json.load(f)
|
625
642
|
return metadata
|
626
643
|
|
wandb/sdk/internal/sender.py
CHANGED
@@ -311,7 +311,10 @@ class SendManager:
|
|
311
311
|
self._output_raw_file = None
|
312
312
|
|
313
313
|
# job builder
|
314
|
-
self._job_builder = JobBuilder(
|
314
|
+
self._job_builder = JobBuilder(
|
315
|
+
settings,
|
316
|
+
files_dir=settings.files_dir,
|
317
|
+
)
|
315
318
|
|
316
319
|
time_now = time.monotonic()
|
317
320
|
self._debounce_config_time = time_now
|
@@ -343,7 +346,7 @@ class SendManager:
|
|
343
346
|
publish_interface = InterfaceQueue(record_q=record_q)
|
344
347
|
context_keeper = context.ContextKeeper()
|
345
348
|
return SendManager(
|
346
|
-
settings=SettingsStatic(settings
|
349
|
+
settings=SettingsStatic(dict(settings)),
|
347
350
|
record_q=record_q,
|
348
351
|
result_q=result_q,
|
349
352
|
interface=publish_interface,
|
@@ -2,9 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from typing import Any, Iterable
|
4
4
|
|
5
|
-
from wandb.
|
6
|
-
from wandb.sdk.lib import RunMoment
|
7
|
-
from wandb.sdk.wandb_settings import CLIENT_ONLY_SETTINGS, Settings
|
5
|
+
from wandb.sdk.wandb_settings import Settings
|
8
6
|
|
9
7
|
|
10
8
|
class SettingsStatic(Settings):
|
@@ -14,87 +12,9 @@ class SettingsStatic(Settings):
|
|
14
12
|
attributes or items.
|
15
13
|
"""
|
16
14
|
|
17
|
-
def __init__(self,
|
18
|
-
data = self._proto_to_dict(proto)
|
15
|
+
def __init__(self, data: dict[str, Any]) -> None:
|
19
16
|
super().__init__(**data)
|
20
17
|
|
21
|
-
def _proto_to_dict(self, proto: wandb_settings_pb2.Settings) -> dict:
|
22
|
-
data = {}
|
23
|
-
|
24
|
-
exclude_fields = {
|
25
|
-
"model_config",
|
26
|
-
"model_fields",
|
27
|
-
"model_fields_set",
|
28
|
-
"__fields__",
|
29
|
-
"__model_fields_set",
|
30
|
-
"__pydantic_self__",
|
31
|
-
"__pydantic_initialised__",
|
32
|
-
}
|
33
|
-
|
34
|
-
fields = (
|
35
|
-
Settings.model_fields
|
36
|
-
if hasattr(Settings, "model_fields")
|
37
|
-
else Settings.__fields__
|
38
|
-
) # type: ignore [attr-defined]
|
39
|
-
|
40
|
-
fields = {k: v for k, v in fields.items() if k not in exclude_fields} # type: ignore [union-attr]
|
41
|
-
|
42
|
-
forks_specified: list[str] = []
|
43
|
-
for key in fields:
|
44
|
-
if key in CLIENT_ONLY_SETTINGS:
|
45
|
-
continue
|
46
|
-
|
47
|
-
value: Any = None
|
48
|
-
|
49
|
-
field_info = fields[key]
|
50
|
-
annotation = str(field_info.annotation)
|
51
|
-
|
52
|
-
if key == "_stats_open_metrics_filters":
|
53
|
-
# todo: it's an underscored field, refactor into
|
54
|
-
# something more elegant?
|
55
|
-
# I'm really about this. It's ugly, but it works.
|
56
|
-
# Do not try to repeat this at home.
|
57
|
-
value_type = getattr(proto, key).WhichOneof("value")
|
58
|
-
if value_type == "sequence":
|
59
|
-
value = list(getattr(proto, key).sequence.value)
|
60
|
-
elif value_type == "mapping":
|
61
|
-
unpacked_mapping = {}
|
62
|
-
for outer_key, outer_value in getattr(
|
63
|
-
proto, key
|
64
|
-
).mapping.value.items():
|
65
|
-
unpacked_inner = {}
|
66
|
-
for inner_key, inner_value in outer_value.value.items():
|
67
|
-
unpacked_inner[inner_key] = inner_value
|
68
|
-
unpacked_mapping[outer_key] = unpacked_inner
|
69
|
-
value = unpacked_mapping
|
70
|
-
elif key == "fork_from" or key == "resume_from":
|
71
|
-
value = getattr(proto, key)
|
72
|
-
if value.run:
|
73
|
-
value = RunMoment(
|
74
|
-
run=value.run, value=value.value, metric=value.metric
|
75
|
-
)
|
76
|
-
forks_specified.append(key)
|
77
|
-
else:
|
78
|
-
value = None
|
79
|
-
else:
|
80
|
-
if proto.HasField(key): # type: ignore [arg-type]
|
81
|
-
value = getattr(proto, key).value
|
82
|
-
# Convert to list if the field is a sequence
|
83
|
-
if any(t in annotation for t in ("tuple", "Sequence", "list")):
|
84
|
-
value = list(value)
|
85
|
-
else:
|
86
|
-
value = None
|
87
|
-
|
88
|
-
if value is not None:
|
89
|
-
data[key] = value
|
90
|
-
|
91
|
-
if len(forks_specified) > 1:
|
92
|
-
raise ValueError(
|
93
|
-
"Only one of fork_from or resume_from can be specified, not both"
|
94
|
-
)
|
95
|
-
|
96
|
-
return data
|
97
|
-
|
98
18
|
def __setattr__(self, name: str, value: object) -> None:
|
99
19
|
raise AttributeError("Error: SettingsStatic is a readonly object")
|
100
20
|
|
wandb/sdk/launch/create_job.py
CHANGED
@@ -417,10 +417,11 @@ def _configure_job_builder_for_partial(tmpdir: str, job_source: str) -> JobBuild
|
|
417
417
|
if job_source == "code":
|
418
418
|
job_source = "artifact"
|
419
419
|
|
420
|
-
settings = wandb.Settings(
|
420
|
+
settings = wandb.Settings(job_source=job_source)
|
421
421
|
job_builder = JobBuilder(
|
422
422
|
settings=settings, # type: ignore
|
423
423
|
verbose=True,
|
424
|
+
files_dir=tmpdir,
|
424
425
|
)
|
425
426
|
job_builder._partial = True
|
426
427
|
# never allow notebook runs
|
@@ -27,7 +27,11 @@ from wandb.sdk.launch.runner.kubernetes_monitor import (
|
|
27
27
|
CustomResource,
|
28
28
|
LaunchKubernetesMonitor,
|
29
29
|
)
|
30
|
-
from wandb.sdk.launch.utils import
|
30
|
+
from wandb.sdk.launch.utils import (
|
31
|
+
recursive_macro_sub,
|
32
|
+
sanitize_identifiers_for_k8s,
|
33
|
+
yield_containers,
|
34
|
+
)
|
31
35
|
from wandb.sdk.lib.retry import ExponentialBackoff, retry_async
|
32
36
|
from wandb.util import get_module
|
33
37
|
|
@@ -39,6 +43,7 @@ from ..utils import (
|
|
39
43
|
MAX_ENV_LENGTHS,
|
40
44
|
PROJECT_SYNCHRONOUS,
|
41
45
|
get_kube_context_and_api_client,
|
46
|
+
make_k8s_label_safe,
|
42
47
|
make_name_dns_safe,
|
43
48
|
)
|
44
49
|
from .abstract import AbstractRun, AbstractRunner
|
@@ -708,6 +713,7 @@ class KubernetesRunner(AbstractRunner):
|
|
708
713
|
api_key_secret: Optional["V1Secret"] = None,
|
709
714
|
wait_for_ready: bool = True,
|
710
715
|
wait_timeout: int = 300,
|
716
|
+
auxiliary_resource_label_value: Optional[str] = None,
|
711
717
|
) -> None:
|
712
718
|
"""Prepare a service for launch.
|
713
719
|
|
@@ -725,6 +731,10 @@ class KubernetesRunner(AbstractRunner):
|
|
725
731
|
config["metadata"].setdefault("labels", {})
|
726
732
|
config["metadata"]["labels"][WANDB_K8S_RUN_ID] = run_id
|
727
733
|
config["metadata"]["labels"]["wandb.ai/created-by"] = "launch-agent"
|
734
|
+
if auxiliary_resource_label_value:
|
735
|
+
config["metadata"]["labels"][WANDB_K8S_LABEL_AUXILIARY_RESOURCE] = (
|
736
|
+
auxiliary_resource_label_value
|
737
|
+
)
|
728
738
|
|
729
739
|
env_vars = launch_project.get_env_vars_dict(
|
730
740
|
self._api, MAX_ENV_LENGTHS[self.__class__.__name__]
|
@@ -734,6 +744,13 @@ class KubernetesRunner(AbstractRunner):
|
|
734
744
|
}
|
735
745
|
add_wandb_env(config, wandb_config_env)
|
736
746
|
|
747
|
+
if auxiliary_resource_label_value:
|
748
|
+
add_label_to_pods(
|
749
|
+
config,
|
750
|
+
WANDB_K8S_LABEL_AUXILIARY_RESOURCE,
|
751
|
+
auxiliary_resource_label_value,
|
752
|
+
)
|
753
|
+
|
737
754
|
if api_key_secret:
|
738
755
|
for cont in yield_containers(config):
|
739
756
|
env = cont.setdefault("env", [])
|
@@ -751,6 +768,8 @@ class KubernetesRunner(AbstractRunner):
|
|
751
768
|
cont["env"] = env
|
752
769
|
|
753
770
|
try:
|
771
|
+
sanitize_identifiers_for_k8s(config)
|
772
|
+
|
754
773
|
await kubernetes_asyncio.utils.create_from_dict(
|
755
774
|
api_client, config, namespace=namespace
|
756
775
|
)
|
@@ -924,6 +943,9 @@ class KubernetesRunner(AbstractRunner):
|
|
924
943
|
additional_services: List[Dict[str, Any]] = recursive_macro_sub(
|
925
944
|
launch_project.launch_spec.get("additional_services", []), update_dict
|
926
945
|
)
|
946
|
+
auxiliary_resource_label_value = make_k8s_label_safe(
|
947
|
+
f"aux-{launch_project.target_entity}-{launch_project.target_project}-{launch_project.run_id}"
|
948
|
+
)
|
927
949
|
if additional_services:
|
928
950
|
wandb.termlog(
|
929
951
|
f"{LOG_PREFIX}Creating additional services: {additional_services}"
|
@@ -943,6 +965,7 @@ class KubernetesRunner(AbstractRunner):
|
|
943
965
|
secret,
|
944
966
|
wait_for_ready,
|
945
967
|
wait_timeout,
|
968
|
+
auxiliary_resource_label_value,
|
946
969
|
)
|
947
970
|
for resource in additional_services
|
948
971
|
if resource.get("config", {})
|
@@ -980,7 +1003,7 @@ class KubernetesRunner(AbstractRunner):
|
|
980
1003
|
job_name,
|
981
1004
|
namespace,
|
982
1005
|
secret,
|
983
|
-
|
1006
|
+
auxiliary_resource_label_value,
|
984
1007
|
)
|
985
1008
|
if self.backend_config[PROJECT_SYNCHRONOUS]:
|
986
1009
|
await submitted_job.wait()
|
@@ -1131,24 +1154,6 @@ async def maybe_create_imagepull_secret(
|
|
1131
1154
|
raise LaunchError(f"Exception when creating Kubernetes secret: {str(e)}\n")
|
1132
1155
|
|
1133
1156
|
|
1134
|
-
def yield_containers(root: Any) -> Iterator[dict]:
|
1135
|
-
"""Yield all container specs in a manifest.
|
1136
|
-
|
1137
|
-
Recursively traverses the manifest and yields all container specs. Container
|
1138
|
-
specs are identified by the presence of a "containers" key in the value.
|
1139
|
-
"""
|
1140
|
-
if isinstance(root, dict):
|
1141
|
-
for k, v in root.items():
|
1142
|
-
if k == "containers":
|
1143
|
-
if isinstance(v, list):
|
1144
|
-
yield from v
|
1145
|
-
elif isinstance(v, (dict, list)):
|
1146
|
-
yield from yield_containers(v)
|
1147
|
-
elif isinstance(root, list):
|
1148
|
-
for item in root:
|
1149
|
-
yield from yield_containers(item)
|
1150
|
-
|
1151
|
-
|
1152
1157
|
def add_wandb_env(root: Union[dict, list], env_vars: Dict[str, str]) -> None:
|
1153
1158
|
"""Injects wandb environment variables into specs.
|
1154
1159
|
|