cortexgrid 0.2.94__tar.gz → 0.2.95__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cortexgrid
3
- Version: 0.2.94
3
+ Version: 0.2.95
4
4
  Summary: Connect your ML code to the RoboLab compute cluster — Ray, MLflow, and S3
5
5
  Project-URL: Homepage, https://github.com/robodatalab/cortexgrid
6
6
  Project-URL: Repository, https://github.com/robodatalab/cortexgrid
@@ -77,11 +77,11 @@ from cortexgrid.model_storage import (
77
77
  IMPORTED,
78
78
  SavedModel,
79
79
  delete_model,
80
- import_model,
81
80
  list_models,
82
81
  load_model,
83
82
  model_registry_status,
84
83
  )
84
+ from cortexgrid.model_storage import import_model as _import_model_storage
85
85
  from cortexgrid.model_storage import save_model as _save_model_storage
86
86
  from cortexgrid.model_serving import (
87
87
  Deployment,
@@ -137,6 +137,27 @@ def save_model(
137
137
  )
138
138
 
139
139
 
140
+ def import_model(
141
+ source: str | Path | Callable[[], str | Path],
142
+ serve_app: type,
143
+ family: str,
144
+ suffix: str,
145
+ ) -> SavedModel:
146
+ """Register a model produced elsewhere once, reuse it on every later call,
147
+ and record on the current Experiment's run which imported model it used.
148
+
149
+ The model belongs to no run (see `cortexgrid.model_storage.import_model`),
150
+ so the run keeps the link instead: the tag
151
+ `imported_model/<family>/<suffix>` holds the version's `created_at`, set
152
+ whether this call uploaded the model or reused it."""
153
+ experiment = Experiment.get_instance()
154
+ model = _import_model_storage(source, serve_app, family, suffix)
155
+ get_mlflow_client().set_tag(
156
+ experiment.run_id, f"imported_model/{family}/{suffix}", model.created_at
157
+ )
158
+ return model
159
+
160
+
140
161
  __all__ = [
141
162
  "Experiment",
142
163
  "delete_experiment",
@@ -50,9 +50,10 @@ _PHASE_UPLOAD_FAILED = "upload_failed"
50
50
  _PHASE_BROKEN = "broken"
51
51
 
52
52
  # An upload still marked "uploading" this long after the version was created is
53
- # treated as broken: save_model creates the version immediately before the
54
- # upload begins, so creation_timestamp is the upload start, and a process that
55
- # dies mid-upload never flips the tag to "ready"/"upload_failed". Expiry is
53
+ # treated as broken: the version is created before the weights are resolved
54
+ # (for `import_model`, before its download), so creation_timestamp is the
55
+ # start of the whole upload, and a process that dies mid-way never flips the
56
+ # tag to "ready"/"upload_failed". Expiry is
56
57
  # derived lazily on read (see `_phase_for`); nothing is written back.
57
58
  _UPLOAD_DEADLINE = timedelta(hours=3)
58
59
 
@@ -178,7 +179,9 @@ def import_model(
178
179
 
179
180
  `source` is the weights directory, or a callable returning it; the callable
180
181
  runs only when the upload actually happens, so an expensive download can be
181
- skipped on every run after the first.
182
+ skipped on every run after the first. It runs after the version is
183
+ registered as "uploading", so a concurrent import sees this one in flight
184
+ while it downloads.
182
185
 
183
186
  If a version is already registered under the key:
184
187
  - "ready": no-op, returns it. `source` and `serve_app` are ignored; to
@@ -199,30 +202,32 @@ def import_model(
199
202
  "another process"
200
203
  )
201
204
  delete_model(family, suffix, IMPORTED)
202
- weights_dir = source() if callable(source) else source
203
- return _upload_model(weights_dir, serve_app, suffix, family, None, IMPORTED)
205
+ return _upload_model(source, serve_app, suffix, family, None, IMPORTED)
204
206
 
205
207
 
206
208
  def _upload_model(
207
- weights_dir: str | Path,
209
+ weights: str | Path | Callable[[], str | Path],
208
210
  serve_app: type,
209
211
  suffix: str,
210
212
  family: str,
211
213
  run_id: str | None,
212
214
  run_name: str,
213
215
  ) -> SavedModel:
214
- """Register a ModelVersion in "uploading", upload the weights and the
215
- serve-app bundle, and flip it to "ready" (or "upload_failed")."""
216
+ """Register a ModelVersion in "uploading", resolve `weights` to a directory
217
+ (calling it when it is a callable), upload the weights and the serve-app
218
+ bundle, and flip it to "ready" (or "upload_failed")."""
216
219
  bucket = get_s3_bucket()
217
220
  prefix = f"models/{run_name}/{family}/{suffix}"
218
- size_bytes = _dir_size_bytes(weights_dir)
219
221
  source = f"s3://{bucket}/{prefix}/weights/"
220
222
  name = f"{family}__{suffix}"
221
223
  client = MlflowClient(tracking_uri=get_mlflow_tracking_uri())
222
224
  _ensure_registered_model(client, name)
223
- # Register the version up front in the "uploading" phase so the dashboard
224
- # can surface a model while its weights are still streaming to storage. The
225
- # bundle tags and the flip to "ready" happen only after the upload lands.
225
+ # Register the version up front in the "uploading" phase, before `weights`
226
+ # is resolved: the dashboard surfaces the model while it is still being
227
+ # fetched and uploaded, and a concurrent `import_model` sees the import in
228
+ # flight for the whole download instead of starting one of its own. The
229
+ # size is stamped once the directory exists; the bundle tags and the flip
230
+ # to "ready" happen only after the upload lands.
226
231
  version = client.create_model_version(
227
232
  name=name,
228
233
  source=source,
@@ -231,11 +236,14 @@ def _upload_model(
231
236
  "family": family,
232
237
  "suffix": suffix,
233
238
  "run_name": run_name,
234
- "size_bytes": str(size_bytes),
235
239
  _LIFECYCLE_TAG: _PHASE_UPLOADING,
236
240
  },
237
241
  )
238
242
  try:
243
+ weights_dir = weights() if callable(weights) else weights
244
+ client.set_model_version_tag(
245
+ name, version.version, "size_bytes", str(_dir_size_bytes(weights_dir))
246
+ )
239
247
  s3_util.upload_dir(str(weights_dir), dest_path=f"{prefix}/weights")
240
248
  bundle_meta = bundle_class(serve_app, family, suffix, run_name)
241
249
  for key, value in metadata_to_tags(bundle_meta).items():
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cortexgrid"
3
- version = "0.2.94"
3
+ version = "0.2.95"
4
4
  description = "Connect your ML code to the RoboLab compute cluster — Ray, MLflow, and S3"
5
5
  readme = "docs/cortexgrid/README.md"
6
6
  license = "Apache-2.0"
File without changes
File without changes