modal 0.73.8__py3-none-any.whl → 0.73.10__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.
@@ -25,6 +25,7 @@ from grpclib import Status
25
25
  from synchronicity.async_wrap import asynccontextmanager
26
26
 
27
27
  import modal_proto.api_pb2
28
+ from modal._runtime import gpu_memory_snapshot
28
29
  from modal._serialization import deserialize, serialize, serialize_data_format
29
30
  from modal._traceback import extract_traceback, print_exception
30
31
  from modal._utils.async_utils import TaskContext, asyncify, synchronize_api, synchronizer
@@ -877,6 +878,17 @@ class _ContainerIOManager:
877
878
  if value != "":
878
879
  config.override_locally(key, value)
879
880
 
881
+ # Restore GPU memory.
882
+ if self.function_def._experimental_enable_gpu_snapshot and self.function_def.resources.gpu_config.gpu_type:
883
+ logger.debug("GPU memory snapshot enabled. Attempting to restore GPU memory.")
884
+ gpu_process_state = gpu_memory_snapshot.get_state()
885
+ if gpu_process_state != gpu_memory_snapshot.CudaCheckpointState.CHECKPOINTED:
886
+ raise ValueError(
887
+ "Cannot restore GPU state if GPU isn't in a 'checkpointed' state. "
888
+ f"Current GPU state: {gpu_process_state}"
889
+ )
890
+ gpu_memory_snapshot.toggle()
891
+
880
892
  # Restore input to default state.
881
893
  self.current_input_id = None
882
894
  self.current_inputs = {}
@@ -892,6 +904,18 @@ class _ContainerIOManager:
892
904
 
893
905
  # Pause heartbeats since they keep the client connection open which causes the snapshotter to crash
894
906
  async with self.heartbeat_condition:
907
+ # Snapshot GPU memory.
908
+ if self.function_def._experimental_enable_gpu_snapshot and self.function_def.resources.gpu_config.gpu_type:
909
+ logger.debug("GPU memory snapshot enabled. Attempting to snapshot GPU memory.")
910
+ gpu_process_state = gpu_memory_snapshot.get_state()
911
+ if gpu_process_state != gpu_memory_snapshot.CudaCheckpointState.RUNNING:
912
+ raise ValueError(
913
+ "Cannot snapshot GPU state if it isn't running. " f"Current GPU state: {gpu_process_state}"
914
+ )
915
+
916
+ gpu_memory_snapshot.toggle()
917
+ gpu_memory_snapshot.wait_for_state(gpu_memory_snapshot.CudaCheckpointState.CHECKPOINTED)
918
+
895
919
  # Notify the heartbeat loop that the snapshot phase has begun in order to
896
920
  # prevent it from sending heartbeat RPCs
897
921
  self._waiting_for_memory_snapshot = True
@@ -0,0 +1,104 @@
1
+ # Copyright Modal Labs 2022
2
+ #
3
+ # This module provides a simple interface for creating GPU memory snapshots,
4
+ # provising a convenient interface to `cuda-checkpoint` [1]. This is intended
5
+ # to be used in conjunction with memory snapshots.
6
+ #
7
+ # [1] https://github.com/NVIDIA/cuda-checkpoint
8
+
9
+ import os
10
+ import subprocess
11
+ import time
12
+ from enum import Enum
13
+
14
+ from modal.config import config, logger
15
+
16
+ CUDA_CHECKPOINT_PATH: str = config.get("cuda_checkpoint_path")
17
+
18
+
19
+ class CudaCheckpointState(Enum):
20
+ """State representation from the CUDA API: https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__TYPES.html#group__CUDA__TYPES_1gc96cdda177a2b8c296144567cbea4f23"""
21
+
22
+ RUNNING = "running"
23
+ LOCKED = "locked"
24
+ CHECKPOINTED = "checkpointed"
25
+ FAILED = "failed"
26
+
27
+
28
+ class CudaCheckpointException(Exception):
29
+ pass
30
+
31
+
32
+ def toggle():
33
+ """Toggle CUDA checkpoint state for current process, moving GPU memory to the
34
+ CPU and back depending on the current process state when called."""
35
+ pid = get_own_pid()
36
+ logger.debug(f"Toggling CUDA checkpoint state for PID {pid}")
37
+
38
+ try:
39
+ cuda_checkpoint_lock_timeout_ms = 5 * 1000
40
+ subprocess.run(
41
+ [
42
+ CUDA_CHECKPOINT_PATH,
43
+ "--toggle",
44
+ "--pid",
45
+ str(pid),
46
+ "--timeout",
47
+ str(cuda_checkpoint_lock_timeout_ms),
48
+ ],
49
+ check=True,
50
+ capture_output=True,
51
+ text=True,
52
+ )
53
+ logger.debug("Successfully toggled CUDA checkpoint state")
54
+
55
+ except subprocess.CalledProcessError as e:
56
+ logger.debug(f"Failed to toggle CUDA checkpoint state: {e.stderr}")
57
+ raise CudaCheckpointException(e.stderr)
58
+
59
+
60
+ def get_state() -> CudaCheckpointState:
61
+ """Get current CUDA checkpoint state for this process."""
62
+ pid = get_own_pid()
63
+
64
+ try:
65
+ result = subprocess.run(
66
+ [CUDA_CHECKPOINT_PATH, "--get-state", "--pid", str(pid)], check=True, capture_output=True, text=True
67
+ )
68
+
69
+ # Parse output to get state
70
+ state_str = result.stdout.strip().lower()
71
+ logger.debug(f"Raw state output: {state_str}")
72
+ return CudaCheckpointState(state_str)
73
+
74
+ except subprocess.CalledProcessError as e:
75
+ logger.debug(f"Failed to get CUDA checkpoint state: {e.stderr}")
76
+ raise CudaCheckpointException(e.stderr)
77
+
78
+
79
+ def wait_for_state(target_state: CudaCheckpointState, timeout_secs: float = 5.0):
80
+ """Wait for CUDA checkpoint to reach a specific state."""
81
+ logger.debug(f"Waiting for CUDA checkpoint state {target_state.value}")
82
+ start_time = time.monotonic()
83
+
84
+ while True:
85
+ current_state = get_state()
86
+
87
+ if current_state == target_state:
88
+ logger.debug(f"Target state {target_state.value} reached")
89
+
90
+ if current_state == CudaCheckpointState.FAILED:
91
+ raise CudaCheckpointException(f"CUDA process state is {current_state}")
92
+
93
+ elapsed = time.monotonic() - start_time
94
+ if elapsed >= timeout_secs:
95
+ raise CudaCheckpointException(f"Timeout after {elapsed:.2f}s waiting for state {target_state.value}")
96
+
97
+ time.sleep(0.1)
98
+
99
+
100
+ def get_own_pid():
101
+ """Returns the Process ID (PID) of the current Python process
102
+ using only the standard library.
103
+ """
104
+ return os.getpid()
modal/cli/run.py CHANGED
@@ -7,6 +7,7 @@ import re
7
7
  import shlex
8
8
  import sys
9
9
  import time
10
+ from dataclasses import dataclass
10
11
  from functools import partial
11
12
  from typing import Any, Callable, Optional, get_type_hints
12
13
 
@@ -35,6 +36,7 @@ class ParameterMetadata(TypedDict):
35
36
  default: Any
36
37
  annotation: Any
37
38
  type_hint: Any
39
+ kind: Any
38
40
 
39
41
 
40
42
  class AnyParamType(click.ParamType):
@@ -58,7 +60,13 @@ class NoParserAvailable(InvalidError):
58
60
  pass
59
61
 
60
62
 
61
- def _get_signature(f: Callable[..., Any], is_method: bool = False) -> dict[str, ParameterMetadata]:
63
+ @dataclass
64
+ class FnSignature:
65
+ parameters: dict[str, ParameterMetadata]
66
+ has_variadic_args: bool
67
+
68
+
69
+ def _get_signature(f: Callable[..., Any], is_method: bool = False) -> FnSignature:
62
70
  try:
63
71
  type_hints = get_type_hints(f)
64
72
  except Exception as exc:
@@ -66,18 +74,29 @@ def _get_signature(f: Callable[..., Any], is_method: bool = False) -> dict[str,
66
74
  msg = "Unable to generate command line interface for app entrypoint. See traceback above for details."
67
75
  raise ExecutionError(msg) from exc
68
76
 
77
+ has_variadic_args = False
78
+
69
79
  if is_method:
70
80
  self = None # Dummy, doesn't matter
71
81
  f = functools.partial(f, self)
82
+
72
83
  signature: dict[str, ParameterMetadata] = {}
73
84
  for param in inspect.signature(f).parameters.values():
74
- signature[param.name] = {
75
- "name": param.name,
76
- "default": param.default,
77
- "annotation": param.annotation,
78
- "type_hint": type_hints.get(param.name, "Any"),
79
- }
80
- return signature
85
+ if param.kind == inspect.Parameter.VAR_POSITIONAL:
86
+ has_variadic_args = True
87
+ else:
88
+ signature[param.name] = {
89
+ "name": param.name,
90
+ "default": param.default,
91
+ "annotation": param.annotation,
92
+ "type_hint": type_hints.get(param.name, "Any"),
93
+ "kind": param.kind,
94
+ }
95
+
96
+ if has_variadic_args and len(signature) > 0:
97
+ raise InvalidError("Functions with variable-length positional arguments (*args) cannot have other parameters.")
98
+
99
+ return FnSignature(signature, has_variadic_args)
81
100
 
82
101
 
83
102
  def _get_param_type_as_str(annot: Any) -> str:
@@ -96,17 +115,18 @@ def _get_param_type_as_str(annot: Any) -> str:
96
115
  return annot_str
97
116
 
98
117
 
99
- def _add_click_options(func, signature: dict[str, ParameterMetadata]):
118
+ def _add_click_options(func, parameters: dict[str, ParameterMetadata]):
100
119
  """Adds @click.option based on function signature
101
120
 
102
121
  Kind of like typer, but using options instead of positional arguments
103
122
  """
104
- for param in signature.values():
123
+ for param in parameters.values():
105
124
  param_type_str = _get_param_type_as_str(param["type_hint"])
106
125
  param_name = param["name"].replace("_", "-")
107
126
  cli_name = "--" + param_name
108
127
  if param_type_str == "bool":
109
128
  cli_name += "/--no-" + param_name
129
+
110
130
  parser = option_parsers.get(param_type_str)
111
131
  if parser is None:
112
132
  msg = f"Parameter `{param_name}` has unparseable annotation: {param['annotation']!r}"
@@ -145,9 +165,15 @@ def _write_local_result(result_path: str, res: Any):
145
165
  fid.write(res)
146
166
 
147
167
 
148
- def _make_click_function(app, inner: Callable[[dict[str, Any]], Any]):
168
+ def _make_click_function(app, signature: FnSignature, inner: Callable[[tuple[str, ...], dict[str, Any]], Any]):
149
169
  @click.pass_context
150
170
  def f(ctx, **kwargs):
171
+ if signature.has_variadic_args:
172
+ assert len(kwargs) == 0
173
+ args = ctx.args
174
+ else:
175
+ args = ()
176
+
151
177
  show_progress: bool = ctx.obj["show_progress"]
152
178
  with enable_output(show_progress):
153
179
  with run_app(
@@ -156,7 +182,7 @@ def _make_click_function(app, inner: Callable[[dict[str, Any]], Any]):
156
182
  environment_name=ctx.obj["env"],
157
183
  interactive=ctx.obj["interactive"],
158
184
  ):
159
- res = inner(kwargs)
185
+ res = inner(args, kwargs)
160
186
 
161
187
  if result_path := ctx.obj["result_path"]:
162
188
  _write_local_result(result_path, res)
@@ -168,23 +194,33 @@ def _get_click_command_for_function(app: App, function: Function):
168
194
  if function.is_generator:
169
195
  raise InvalidError("`modal run` is not supported for generator functions")
170
196
 
171
- signature: dict[str, ParameterMetadata] = _get_signature(function.info.raw_f)
197
+ signature = _get_signature(function.info.raw_f)
198
+
199
+ def _inner(args, click_kwargs):
200
+ return function.remote(*args, **click_kwargs)
172
201
 
173
- def _inner(click_kwargs):
174
- return function.remote(**click_kwargs)
202
+ f = _make_click_function(app, signature, _inner)
175
203
 
176
- f = _make_click_function(app, _inner)
204
+ with_click_options = _add_click_options(f, signature.parameters)
177
205
 
178
- with_click_options = _add_click_options(f, signature)
179
- return click.command(with_click_options)
206
+ if signature.has_variadic_args:
207
+ return click.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})(
208
+ with_click_options
209
+ )
210
+ else:
211
+ return click.command(with_click_options)
180
212
 
181
213
 
182
214
  def _get_click_command_for_cls(app: App, method_ref: MethodReference):
183
- signature: dict[str, ParameterMetadata]
215
+ parameters: dict[str, ParameterMetadata]
184
216
  cls = method_ref.cls
185
217
  method_name = method_ref.method_name
186
218
 
187
219
  cls_signature = _get_signature(cls._get_user_cls())
220
+
221
+ if cls_signature.has_variadic_args:
222
+ raise InvalidError("Modal classes cannot have variable-length positional arguments (*args).")
223
+
188
224
  partial_functions = cls._get_partial_functions()
189
225
 
190
226
  if method_name in ("*", ""):
@@ -202,27 +238,35 @@ def _get_click_command_for_cls(app: App, method_ref: MethodReference):
202
238
  fun_signature = _get_signature(partial_function._get_raw_f(), is_method=True)
203
239
 
204
240
  # TODO(erikbern): assert there's no overlap?
205
- signature = dict(**cls_signature, **fun_signature) # Pool all arguments
241
+ parameters = dict(**cls_signature.parameters, **fun_signature.parameters) # Pool all arguments
206
242
 
207
- def _inner(click_kwargs):
243
+ def _inner(args, click_kwargs):
208
244
  # unpool class and method arguments
209
245
  # TODO(erikbern): this code is a bit hacky
210
- cls_kwargs = {k: click_kwargs[k] for k in cls_signature}
211
- fun_kwargs = {k: click_kwargs[k] for k in fun_signature}
246
+ cls_kwargs = {k: click_kwargs[k] for k in cls_signature.parameters}
247
+ fun_kwargs = {k: click_kwargs[k] for k in fun_signature.parameters}
212
248
 
213
249
  instance = cls(**cls_kwargs)
214
250
  method: Function = getattr(instance, method_name)
215
- return method.remote(**fun_kwargs)
251
+ return method.remote(*args, **fun_kwargs)
252
+
253
+ f = _make_click_function(app, fun_signature, _inner)
254
+ with_click_options = _add_click_options(f, parameters)
216
255
 
217
- f = _make_click_function(app, _inner)
218
- with_click_options = _add_click_options(f, signature)
219
- return click.command(with_click_options)
256
+ if fun_signature.has_variadic_args:
257
+ return click.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})(
258
+ with_click_options
259
+ )
260
+ else:
261
+ return click.command(with_click_options)
220
262
 
221
263
 
222
264
  def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoint):
223
265
  func = entrypoint.info.raw_f
224
266
  isasync = inspect.iscoroutinefunction(func)
225
267
 
268
+ signature = _get_signature(func)
269
+
226
270
  @click.pass_context
227
271
  def f(ctx, *args, **kwargs):
228
272
  if ctx.obj["detach"]:
@@ -231,6 +275,10 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
231
275
  "triggered Modal function alive after the parent process has been killed or disconnected."
232
276
  )
233
277
 
278
+ if signature.has_variadic_args:
279
+ assert len(args) == 0 and len(kwargs) == 0
280
+ args = ctx.args
281
+
234
282
  show_progress: bool = ctx.obj["show_progress"]
235
283
  with enable_output(show_progress):
236
284
  with run_app(
@@ -250,8 +298,14 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
250
298
  if result_path := ctx.obj["result_path"]:
251
299
  _write_local_result(result_path, res)
252
300
 
253
- with_click_options = _add_click_options(f, _get_signature(func))
254
- return click.command(with_click_options)
301
+ with_click_options = _add_click_options(f, signature.parameters)
302
+
303
+ if signature.has_variadic_args:
304
+ return click.command(context_settings={"ignore_unknown_options": True, "allow_extra_args": True})(
305
+ with_click_options
306
+ )
307
+ else:
308
+ return click.command(with_click_options)
255
309
 
256
310
 
257
311
  def _get_runnable_list(all_usable_commands: list[CLICommand]) -> str:
modal/client.pyi CHANGED
@@ -27,7 +27,7 @@ class _Client:
27
27
  _snapshotted: bool
28
28
 
29
29
  def __init__(
30
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.8"
30
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.10"
31
31
  ): ...
32
32
  def is_closed(self) -> bool: ...
33
33
  @property
@@ -85,7 +85,7 @@ class Client:
85
85
  _snapshotted: bool
86
86
 
87
87
  def __init__(
88
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.8"
88
+ self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "0.73.10"
89
89
  ): ...
90
90
  def is_closed(self) -> bool: ...
91
91
  @property
modal/config.py CHANGED
@@ -223,6 +223,7 @@ _SETTINGS = {
223
223
  "strict_parameters": _Setting(False, transform=_to_boolean), # For internal/experimental use
224
224
  "snapshot_debug": _Setting(False, transform=_to_boolean),
225
225
  "client_retries": _Setting(False, transform=_to_boolean), # For internal testing.
226
+ "cuda_checkpoint_path": _Setting("/__modal/.bin/cuda-checkpoint"), # Used for snapshotting GPU memory.
226
227
  }
227
228
 
228
229
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modal
3
- Version: 0.73.8
3
+ Version: 0.73.10
4
4
  Summary: Python client library for Modal
5
5
  Author: Modal Labs
6
6
  Author-email: support@modal.com
@@ -21,12 +21,12 @@ modal/app.py,sha256=wRygVSrWH8iIqhDAAl2Ww_RAkz8MCJZ0Jt9qYZCF6SA,44626
21
21
  modal/app.pyi,sha256=lxiuWzE_OLb3WHg-H7Pek9DGBuCUzZ55P594VhJL5LA,26113
22
22
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
23
23
  modal/client.py,sha256=8SQawr7P1PNUCq1UmJMUQXG2jIo4Nmdcs311XqrNLRE,15276
24
- modal/client.pyi,sha256=kOt17KK6b51zHs9T5vme0cCrLzJu-nw1DTE1kSuxXcQ,7591
24
+ modal/client.pyi,sha256=zquF7hra8lDrBysJUafRuLtmDHnUXpEU1nt9wRDDCUs,7593
25
25
  modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
26
26
  modal/cloud_bucket_mount.pyi,sha256=30T3K1a89l6wzmEJ_J9iWv9SknoGqaZDx59Xs-ZQcmk,1607
27
27
  modal/cls.py,sha256=kNnZrBYVXOhgEXU0rDWk2Hr-bQRrsZkMKDgC-TD_6Bs,31063
28
28
  modal/cls.pyi,sha256=gb6QNwfX3HSJfcZXPY36N9ywF7aBJTwwtoARnf3G1HQ,8877
29
- modal/config.py,sha256=BzhZYUUwOmvVwf6x5kf0ywMC257s648dmuhsnB6g3gk,11041
29
+ modal/config.py,sha256=XT1W4Y9PVkbYMAXjJRshvQEPDhZmnfW_ZRMwl8XKoqA,11149
30
30
  modal/container_process.py,sha256=WTqLn01dJPVkPpwR_0w_JH96ceN5mV4TGtiu1ZR2RRA,6108
31
31
  modal/container_process.pyi,sha256=Hf0J5JyDdCCXBJSKx6gvkPOo0XrztCm78xzxamtzUjQ,2828
32
32
  modal/dict.py,sha256=vc5lQVqzeDUCb4fRjnOlqYK2GmBb0fIhZmvB0xIBG0U,12921
@@ -83,8 +83,9 @@ modal/volume.py,sha256=JAWeDvoAG95tMBv-fYIERyHsJPS_X_xGpxRRmYtb6j0,30096
83
83
  modal/volume.pyi,sha256=kTsXarphjZILXci84LQy7EyC84eXUs5-7D62IM5q3eE,12491
84
84
  modal/_runtime/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
85
85
  modal/_runtime/asgi.py,sha256=c4hmaMW1pLo-cm7ouriJjieuFm4ZF6D2LMy0638sfOs,22139
86
- modal/_runtime/container_io_manager.py,sha256=QVWMCvJatd2696wsauzXl20psxCYsR0d_CHeS5ceTsU,43201
86
+ modal/_runtime/container_io_manager.py,sha256=L6qv-Mo3mN3ttR5GX-G36cUhH_oz8wdP5WG0HT5FFzg,44619
87
87
  modal/_runtime/execution_context.py,sha256=E6ofm6j1POXGPxS841X3V7JU6NheVb8OkQc7JpLq4Kg,2712
88
+ modal/_runtime/gpu_memory_snapshot.py,sha256=vV6igsqN9CxOoH91kUkuaZQ32QfX5wdoXIS-6MIYX2Y,3315
88
89
  modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
89
90
  modal/_runtime/user_code_imports.py,sha256=zl_Mq9dsrVF62x3w-iNK1YAhZKYAXeFaGpd4G7AySTc,14746
90
91
  modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
@@ -124,7 +125,7 @@ modal/cli/launch.py,sha256=pzQt2QlcrbIUU0MVzWWPAvMQ6MCyqsHZ0X9JcV-sY04,3242
124
125
  modal/cli/network_file_system.py,sha256=eq3JnwjbfFNsJodIyANHL06ByYc3BSavzdmu8C96cHA,7948
125
126
  modal/cli/profile.py,sha256=rLXfjJObfPNjaZvNfHGIKqs7y9bGYyGe-K7V0w-Ni0M,3110
126
127
  modal/cli/queues.py,sha256=6gTu76dzBtPN5eQVsLrvQpuru5jI9ZCWK5Eh8J8XhaM,4498
127
- modal/cli/run.py,sha256=_hYvRwBkrbakN3oAuoiaMdYytBtN4BoryrYxPAVIIvc,20065
128
+ modal/cli/run.py,sha256=hXCvqQM6w7dXz0254q5Sr0UNR5JC6cEA6fBdrcshCcg,21784
128
129
  modal/cli/secret.py,sha256=uQpwYrMY98iMCWeZOQTcktOYhPTZ8IHnyealDc2CZqo,4206
129
130
  modal/cli/token.py,sha256=mxSgOWakXG6N71hQb1ko61XAR9ZGkTMZD-Txn7gmTac,1924
130
131
  modal/cli/utils.py,sha256=hZmjyzcPjDnQSkLvycZD2LhGdcsfdZshs_rOU78EpvI,3717
@@ -170,10 +171,10 @@ modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0y
170
171
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
171
172
  modal_version/__init__.py,sha256=wiJQ53c-OMs0Xf1UeXOxQ7FwlV1VzIjnX6o-pRYZ_Pk,470
172
173
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
173
- modal_version/_version_generated.py,sha256=a8LMamoFRZKpp6SoTp5bDH1HRWGljeWLZesEIHf3WIE,148
174
- modal-0.73.8.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
175
- modal-0.73.8.dist-info/METADATA,sha256=wsn98lBqsvSVbab7C3DqlgtiyUF8vmevqqGNf7Ir6WE,2329
176
- modal-0.73.8.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
177
- modal-0.73.8.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
178
- modal-0.73.8.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
179
- modal-0.73.8.dist-info/RECORD,,
174
+ modal_version/_version_generated.py,sha256=bRP1kitLENe-C1R8SFEB0RNXUvVdmynlJRPg8FgsxXA,149
175
+ modal-0.73.10.dist-info/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
176
+ modal-0.73.10.dist-info/METADATA,sha256=o2yy7RC_d68S0vZZyIem-3gJG0H7EDGGXtmHHcPzmEE,2330
177
+ modal-0.73.10.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
178
+ modal-0.73.10.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
179
+ modal-0.73.10.dist-info/top_level.txt,sha256=1nvYbOSIKcmU50fNrpnQnrrOpj269ei3LzgB6j9xGqg,64
180
+ modal-0.73.10.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  # Copyright Modal Labs 2025
2
2
 
3
3
  # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client.
4
- build_number = 8 # git: 6a2791f
4
+ build_number = 10 # git: 6951dc1