modal 1.1.1.dev40__py3-none-any.whl → 1.1.1.dev44__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.

Potentially problematic release.


This version of modal might be problematic. Click here for more details.

@@ -1,17 +1,18 @@
1
1
  # Copyright Modal Labs 2022
2
2
  #
3
3
  # This module provides a simple interface for creating GPU memory snapshots,
4
- # provising a convenient interface to `cuda-checkpoint` [1]. This is intended
4
+ # providing a convenient interface to `cuda-checkpoint` [1]. This is intended
5
5
  # to be used in conjunction with memory snapshots.
6
6
  #
7
7
  # [1] https://github.com/NVIDIA/cuda-checkpoint
8
8
 
9
9
  import subprocess
10
10
  import time
11
- from concurrent.futures import ThreadPoolExecutor
11
+ from concurrent.futures import ThreadPoolExecutor, as_completed
12
12
  from dataclasses import dataclass
13
13
  from enum import Enum
14
14
  from pathlib import Path
15
+ from typing import List, Optional
15
16
 
16
17
  from modal.config import config, logger
17
18
 
@@ -19,7 +20,9 @@ CUDA_CHECKPOINT_PATH: str = config.get("cuda_checkpoint_path")
19
20
 
20
21
 
21
22
  class CudaCheckpointState(Enum):
22
- """State representation from the CUDA API: https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__TYPES.html#group__CUDA__TYPES_1gc96cdda177a2b8c296144567cbea4f23"""
23
+ """State representation from the CUDA API [1].
24
+
25
+ [1] https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__TYPES.html"""
23
26
 
24
27
  RUNNING = "running"
25
28
  LOCKED = "locked"
@@ -28,6 +31,8 @@ class CudaCheckpointState(Enum):
28
31
 
29
32
 
30
33
  class CudaCheckpointException(Exception):
34
+ """Exception raised for CUDA checkpoint operations."""
35
+
31
36
  pass
32
37
 
33
38
 
@@ -39,16 +44,31 @@ class CudaCheckpointProcess:
39
44
  pid: int
40
45
  state: CudaCheckpointState
41
46
 
42
- def toggle(self, target_state: CudaCheckpointState, timeout_secs: float = 5 * 60.0):
47
+ def toggle(self, target_state: CudaCheckpointState, timeout_secs: float = 5 * 60.0) -> None:
43
48
  """Toggle CUDA checkpoint state for current process, moving GPU memory to the
44
- CPU and back depending on the current process state when called."""
49
+ CPU and back depending on the current process state when called.
50
+ """
45
51
  logger.debug(f"PID: {self.pid} Toggling CUDA checkpoint state to {target_state.value}")
46
52
 
47
53
  start_time = time.monotonic()
54
+ retry_count = 0
55
+ max_retries = 3
48
56
 
49
57
  while self._should_continue_toggle(target_state, start_time, timeout_secs):
50
- self._execute_toggle_command()
51
- time.sleep(0.1)
58
+ try:
59
+ self._execute_toggle_command()
60
+ # Use exponential backoff for retries
61
+ sleep_time = min(0.1 * (2**retry_count), 1.0)
62
+ time.sleep(sleep_time)
63
+ retry_count = 0
64
+ except CudaCheckpointException as e:
65
+ retry_count += 1
66
+ if retry_count >= max_retries:
67
+ raise CudaCheckpointException(
68
+ f"PID: {self.pid} Failed to toggle state after {max_retries} retries: {e}"
69
+ )
70
+ logger.debug(f"PID: {self.pid} Retry {retry_count}/{max_retries} after error: {e}")
71
+ time.sleep(0.5 * retry_count)
52
72
 
53
73
  logger.debug(f"PID: {self.pid} Target state {target_state.value} reached")
54
74
 
@@ -73,19 +93,25 @@ class CudaCheckpointProcess:
73
93
 
74
94
  return True
75
95
 
76
- def _execute_toggle_command(self):
96
+ def _execute_toggle_command(self) -> None:
77
97
  """Execute the cuda-checkpoint toggle command."""
78
98
  try:
79
- subprocess.run(
99
+ _ = subprocess.run(
80
100
  [CUDA_CHECKPOINT_PATH, "--toggle", "--pid", str(self.pid)],
81
101
  check=True,
82
102
  capture_output=True,
83
103
  text=True,
104
+ timeout=30,
84
105
  )
85
106
  logger.debug(f"PID: {self.pid} Successfully toggled CUDA checkpoint state")
86
107
  except subprocess.CalledProcessError as e:
87
- logger.debug(f"PID: {self.pid} Failed to toggle CUDA checkpoint state: {e.stderr}")
88
- raise CudaCheckpointException(e.stderr)
108
+ error_msg = f"PID: {self.pid} Failed to toggle CUDA checkpoint state: {e.stderr}"
109
+ logger.debug(error_msg)
110
+ raise CudaCheckpointException(error_msg)
111
+ except subprocess.TimeoutExpired:
112
+ error_msg = f"PID: {self.pid} Toggle command timed out"
113
+ logger.debug(error_msg)
114
+ raise CudaCheckpointException(error_msg)
89
115
 
90
116
  def refresh_state(self) -> None:
91
117
  """Refreshes the current CUDA checkpoint state for this process."""
@@ -95,15 +121,20 @@ class CudaCheckpointProcess:
95
121
  check=True,
96
122
  capture_output=True,
97
123
  text=True,
98
- timeout=5,
124
+ timeout=10,
99
125
  )
100
126
 
101
127
  state_str = result.stdout.strip().lower()
102
128
  self.state = CudaCheckpointState(state_str)
103
129
 
104
130
  except subprocess.CalledProcessError as e:
105
- logger.debug(f"PID: {self.pid} Failed to get CUDA checkpoint state: {e.stderr}")
106
- raise CudaCheckpointException(e.stderr)
131
+ error_msg = f"PID: {self.pid} Failed to get CUDA checkpoint state: {e.stderr}"
132
+ logger.debug(error_msg)
133
+ raise CudaCheckpointException(error_msg)
134
+ except subprocess.TimeoutExpired:
135
+ error_msg = f"PID: {self.pid} Get state command timed out"
136
+ logger.debug(error_msg)
137
+ raise CudaCheckpointException(error_msg)
107
138
 
108
139
 
109
140
  class CudaCheckpointSession:
@@ -111,12 +142,17 @@ class CudaCheckpointSession:
111
142
 
112
143
  def __init__(self):
113
144
  self.cuda_processes = self._get_cuda_pids()
114
- logger.debug(f"PIDs with CUDA sessions: {[c.pid for c in self.cuda_processes]}")
145
+ if self.cuda_processes:
146
+ logger.debug(
147
+ f"Found {len(self.cuda_processes)} PID(s) with CUDA sessions: {[c.pid for c in self.cuda_processes]}"
148
+ )
149
+ else:
150
+ logger.debug("No CUDA sessions found.")
115
151
 
116
- def _get_cuda_pids(self) -> list[CudaCheckpointProcess]:
152
+ def _get_cuda_pids(self) -> List[CudaCheckpointProcess]:
117
153
  """Iterates over all PIDs and identifies the ones that have running
118
154
  CUDA sessions."""
119
- cuda_pids: list[CudaCheckpointProcess] = []
155
+ cuda_pids: List[CudaCheckpointProcess] = []
120
156
 
121
157
  # Get all active process IDs from /proc directory
122
158
  proc_dir = Path("/proc")
@@ -125,75 +161,143 @@ class CudaCheckpointSession:
125
161
  "OS does not have /proc path rendering it incompatible with GPU memory snapshots."
126
162
  )
127
163
 
128
- for entry in proc_dir.iterdir():
129
- if not entry.name.isdigit():
130
- continue
131
-
132
- pid = int(entry.name)
133
- try:
134
- # Call cuda-checkpoint to check if this PID has a CUDA session
135
- result = subprocess.run(
136
- [CUDA_CHECKPOINT_PATH, "--get-state", "--pid", str(pid)],
137
- capture_output=True,
138
- text=True,
139
- timeout=10,
140
- )
141
-
142
- # If the command succeeds (return code 0), this PID has a CUDA session
143
- if result.returncode == 0:
144
- state_str = result.stdout.strip().lower()
145
- state = CudaCheckpointState(state_str)
146
-
147
- cuda_checkpoint_process = CudaCheckpointProcess(pid=pid, state=state)
148
- cuda_pids.append(cuda_checkpoint_process)
164
+ # Get all numeric directories (PIDs) from /proc
165
+ pid_dirs = [entry for entry in proc_dir.iterdir() if entry.name.isdigit()]
149
166
 
150
- # Command failed, which is expected for PIDs without CUDA sessions
151
- except subprocess.CalledProcessError:
152
- continue
167
+ # Use ThreadPoolExecutor to check PIDs in parallel for better performance
168
+ with ThreadPoolExecutor(max_workers=min(50, len(pid_dirs))) as executor:
169
+ future_to_pid = {
170
+ executor.submit(self._check_cuda_session, int(entry.name)): int(entry.name) for entry in pid_dirs
171
+ }
153
172
 
154
- # Raise other exceptions
155
- except subprocess.TimeoutExpired:
156
- raise CudaCheckpointException(f"Failed to get CUDA state for PID {pid}")
157
- except Exception as e:
158
- raise CudaCheckpointException(e)
173
+ for future in as_completed(future_to_pid):
174
+ pid = future_to_pid[future]
175
+ try:
176
+ cuda_process = future.result()
177
+ if cuda_process:
178
+ cuda_pids.append(cuda_process)
179
+ except Exception as e:
180
+ logger.debug(f"Error checking PID {pid}: {e}")
159
181
 
160
182
  # Sort PIDs for ordered checkpointing
161
183
  cuda_pids.sort(key=lambda x: x.pid)
162
184
  return cuda_pids
163
185
 
186
+ def _check_cuda_session(self, pid: int) -> Optional[CudaCheckpointProcess]:
187
+ """Check if a specific PID has a CUDA session."""
188
+ try:
189
+ result = subprocess.run(
190
+ [CUDA_CHECKPOINT_PATH, "--get-state", "--pid", str(pid)],
191
+ capture_output=True,
192
+ text=True,
193
+ timeout=5,
194
+ )
195
+
196
+ # If the command succeeds (return code 0), this PID has a CUDA session
197
+ if result.returncode == 0:
198
+ state_str = result.stdout.strip().lower()
199
+ state = CudaCheckpointState(state_str)
200
+ return CudaCheckpointProcess(pid=pid, state=state)
201
+
202
+ except subprocess.CalledProcessError:
203
+ # Command failed, which is expected for PIDs without CUDA sessions
204
+ pass
205
+ except subprocess.TimeoutExpired:
206
+ logger.debug(f"Timeout checking CUDA state for PID {pid}")
207
+ except Exception as e:
208
+ logger.debug(f"Error checking PID {pid}: {e}")
209
+
210
+ return None
211
+
164
212
  def checkpoint(self) -> None:
213
+ """Checkpoint all CUDA processes, moving GPU memory to CPU."""
214
+ if not self.cuda_processes:
215
+ logger.debug("No CUDA processes to checkpoint.")
216
+ return
217
+
165
218
  # Validate all states first
166
219
  for proc in self.cuda_processes:
220
+ proc.refresh_state() # Refresh state before validation
167
221
  if proc.state != CudaCheckpointState.RUNNING:
168
- raise CudaCheckpointException(f"CUDA session not in {CudaCheckpointState.RUNNING} state.")
222
+ raise CudaCheckpointException(
223
+ f"PID {proc.pid}: CUDA session not in {CudaCheckpointState.RUNNING.value} state. "
224
+ f"Current state: {proc.state.value}"
225
+ )
169
226
 
170
227
  # Moving state from GPU to CPU can take several seconds per CUDA session.
171
228
  # Make a parallel call per CUDA session.
172
229
  start = time.perf_counter()
173
230
 
174
- def checkpoint_impl(proc: CudaCheckpointProcess):
231
+ def checkpoint_impl(proc: CudaCheckpointProcess) -> None:
175
232
  proc.toggle(CudaCheckpointState.CHECKPOINTED)
176
233
 
177
234
  with ThreadPoolExecutor() as executor:
178
- list(executor.map(checkpoint_impl, self.cuda_processes))
235
+ futures = [executor.submit(checkpoint_impl, proc) for proc in self.cuda_processes]
236
+
237
+ # Wait for all futures and collect any exceptions
238
+ exceptions = []
239
+ for future in as_completed(futures):
240
+ try:
241
+ future.result()
242
+ except Exception as e:
243
+ exceptions.append(e)
244
+
245
+ if exceptions:
246
+ raise CudaCheckpointException(
247
+ f"Failed to checkpoint {len(exceptions)} processes: {'; '.join(str(e) for e in exceptions)}"
248
+ )
179
249
 
180
250
  elapsed = time.perf_counter() - start
181
- logger.debug(f"Checkpointing CUDA sessions took => {elapsed:.3f}s")
251
+ logger.debug(f"Checkpointing {len(self.cuda_processes)} CUDA sessions took => {elapsed:.3f}s")
182
252
 
183
253
  def restore(self) -> None:
254
+ """Restore all CUDA processes, moving memory back from CPU to GPU."""
255
+ if not self.cuda_processes:
256
+ logger.debug("No CUDA sessions to restore.")
257
+ return
258
+
184
259
  # Validate all states first
185
260
  for proc in self.cuda_processes:
261
+ proc.refresh_state() # Refresh state before validation
186
262
  if proc.state != CudaCheckpointState.CHECKPOINTED:
187
- raise CudaCheckpointException(f"CUDA session not in {CudaCheckpointState.CHECKPOINTED} state.")
263
+ raise CudaCheckpointException(
264
+ f"PID {proc.pid}: CUDA session not in {CudaCheckpointState.CHECKPOINTED.value} state. "
265
+ f"Current state: {proc.state.value}"
266
+ )
188
267
 
189
268
  # See checkpoint() for rationale about parallelism.
190
269
  start = time.perf_counter()
191
270
 
192
- def restore_process(proc: CudaCheckpointProcess):
271
+ def restore_process(proc: CudaCheckpointProcess) -> None:
193
272
  proc.toggle(CudaCheckpointState.RUNNING)
194
273
 
195
274
  with ThreadPoolExecutor() as executor:
196
- list(executor.map(restore_process, self.cuda_processes))
275
+ futures = [executor.submit(restore_process, proc) for proc in self.cuda_processes]
276
+
277
+ # Wait for all futures and collect any exceptions
278
+ exceptions = []
279
+ for future in as_completed(futures):
280
+ try:
281
+ future.result()
282
+ except Exception as e:
283
+ exceptions.append(e)
284
+
285
+ if exceptions:
286
+ raise CudaCheckpointException(
287
+ f"Failed to restore {len(exceptions)} processes: {'; '.join(str(e) for e in exceptions)}"
288
+ )
197
289
 
198
290
  elapsed = time.perf_counter() - start
199
- logger.debug(f"Restoring CUDA sessions took => {elapsed:.3f}s")
291
+ logger.debug(f"Restoring {len(self.cuda_processes)} CUDA session(s) took => {elapsed:.3f}s")
292
+
293
+ def get_process_count(self) -> int:
294
+ """Get the number of CUDA processes managed by this session."""
295
+ return len(self.cuda_processes)
296
+
297
+ def get_process_states(self) -> List[tuple[int, CudaCheckpointState]]:
298
+ """Get current states of all managed processes."""
299
+ states = []
300
+ for proc in self.cuda_processes:
301
+ proc.refresh_state()
302
+ states.append((proc.pid, proc.state))
303
+ return states
modal/client.pyi CHANGED
@@ -33,7 +33,7 @@ class _Client:
33
33
  server_url: str,
34
34
  client_type: int,
35
35
  credentials: typing.Optional[tuple[str, str]],
36
- version: str = "1.1.1.dev40",
36
+ version: str = "1.1.1.dev44",
37
37
  ):
38
38
  """mdmd:hidden
39
39
  The Modal client object is not intended to be instantiated directly by users.
@@ -164,7 +164,7 @@ class Client:
164
164
  server_url: str,
165
165
  client_type: int,
166
166
  credentials: typing.Optional[tuple[str, str]],
167
- version: str = "1.1.1.dev40",
167
+ version: str = "1.1.1.dev44",
168
168
  ):
169
169
  """mdmd:hidden
170
170
  The Modal client object is not intended to be instantiated directly by users.
modal/functions.pyi CHANGED
@@ -427,7 +427,7 @@ class Function(
427
427
 
428
428
  _call_generator: ___call_generator_spec[typing_extensions.Self]
429
429
 
430
- class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
430
+ class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
431
431
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
432
432
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
433
433
  ...
@@ -436,7 +436,7 @@ class Function(
436
436
  """Calls the function remotely, executing it with the given arguments and returning the execution's result."""
437
437
  ...
438
438
 
439
- remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
439
+ remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
440
440
 
441
441
  class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
442
442
  def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
@@ -463,7 +463,7 @@ class Function(
463
463
  """
464
464
  ...
465
465
 
466
- class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
466
+ class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
467
467
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
468
468
  """[Experimental] Calls the function with the given arguments, without waiting for the results.
469
469
 
@@ -487,7 +487,7 @@ class Function(
487
487
  ...
488
488
 
489
489
  _experimental_spawn: ___experimental_spawn_spec[
490
- modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
490
+ modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
491
491
  ]
492
492
 
493
493
  class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
@@ -496,7 +496,7 @@ class Function(
496
496
 
497
497
  _spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
498
498
 
499
- class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
499
+ class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
500
500
  def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
501
501
  """Calls the function with the given arguments, without waiting for the results.
502
502
 
@@ -517,7 +517,7 @@ class Function(
517
517
  """
518
518
  ...
519
519
 
520
- spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
520
+ spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
521
521
 
522
522
  def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
523
523
  """Return the inner Python object wrapped by this Modal Function."""
modal/image.py CHANGED
@@ -1393,7 +1393,13 @@ class _Image(_Object, type_prefix="im"):
1393
1393
  # a requirement in `uv.lock`
1394
1394
  return
1395
1395
 
1396
- dependencies = pyproject_toml_content["project"]["dependencies"]
1396
+ try:
1397
+ dependencies = pyproject_toml_content["project"]["dependencies"]
1398
+ except KeyError as e:
1399
+ raise InvalidError(
1400
+ f"Invalid pyproject.toml file: missing key {e} in {pyproject_toml}. "
1401
+ "See https://packaging.python.org/en/latest/guides/writing-pyproject-toml for guidelines."
1402
+ )
1397
1403
 
1398
1404
  for group in groups:
1399
1405
  if (
@@ -1459,7 +1465,7 @@ class _Image(_Object, type_prefix="im"):
1459
1465
  commands.append(f"COPY /.uv.lock {UV_ROOT}/uv.lock")
1460
1466
 
1461
1467
  if frozen:
1462
- # Do not update `uv.lock` when we have one when `frozen=True`. This it ehd efault because this
1468
+ # Do not update `uv.lock` when we have one when `frozen=True`. This is the default because this
1463
1469
  # ensures that the runtime environment matches the local `uv.lock`.
1464
1470
  #
1465
1471
  # If `frozen=False`, then `uv sync` will update the the dependencies in the `uv.lock` file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.1.dev40
3
+ Version: 1.1.1.dev44
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.0
@@ -22,7 +22,7 @@ modal/app.py,sha256=kpq4kXp7pch688y6g55QYAC10wqPTU5FXKoWPMirA3E,47899
22
22
  modal/app.pyi,sha256=-jKXlGDBWRPVsuenBhdMRqawK-L2eiJ7gHbmSblhltg,43525
23
23
  modal/call_graph.py,sha256=1g2DGcMIJvRy-xKicuf63IVE98gJSnQsr8R_NVMptNc,2581
24
24
  modal/client.py,sha256=kyAIVB3Ay-XKJizQ_1ufUFB__EagV0MLmHJpyYyJ7J0,18636
25
- modal/client.pyi,sha256=PNk58EEgTKJjy4hA9vN9sClYMF9gkjfbuFmCfJ3Ujfs,15831
25
+ modal/client.pyi,sha256=vIhdpiz_vkRCPexKcG97KNhPQ20G-QFeJRcPeLYiQfQ,15831
26
26
  modal/cloud_bucket_mount.py,sha256=YOe9nnvSr4ZbeCn587d7_VhE9IioZYRvF9VYQTQux08,5914
27
27
  modal/cloud_bucket_mount.pyi,sha256=-qSfYAQvIoO_l2wsCCGTG5ZUwQieNKXdAO00yP1-LYU,7394
28
28
  modal/cls.py,sha256=7A0xGnugQzm8dOfnKMjLjtqekRlRtQ0jPFRYgq6xdUM,40018
@@ -39,9 +39,9 @@ modal/file_io.py,sha256=BVqAJ0sgPUfN8QsYztWiGB4j56he60TncM02KsylnCw,21449
39
39
  modal/file_io.pyi,sha256=cPT_hsplE5iLCXhYOLn1Sp9eDdk7DxdFmicQHanJZyg,15918
40
40
  modal/file_pattern_matcher.py,sha256=urAue8es8jxqX94k9EYoZxxhtfgOlsEES8lbFHOorzc,7734
41
41
  modal/functions.py,sha256=kcNHvqeGBxPI7Cgd57NIBBghkfbeFJzXO44WW0jSmao,325
42
- modal/functions.pyi,sha256=cS7QYPHcFD_p95hrAU-TWxBIraIzzG-uO9KyPAwfxdw,34766
42
+ modal/functions.pyi,sha256=VYPnfnCLfHRDAl6t5AaVHmZEz0T5f2igjTeMOtHPie8,34766
43
43
  modal/gpu.py,sha256=Fe5ORvVPDIstSq1xjmM6OoNgLYFWvogP9r5BgmD3hYg,6769
44
- modal/image.py,sha256=Gm1TGIy1IzaNaYG659C6Jl_flPe6YYzGdSRpf0epDAg,102833
44
+ modal/image.py,sha256=A83nmo0zfCUwgvJh0LZ7Yc1sYvDnZLl_phbKxN-9HIw,103144
45
45
  modal/image.pyi,sha256=mlGwmpkKdwNK_VRrCa0WMDURmQPSOohm7hgiiFTfdXI,68541
46
46
  modal/io_streams.py,sha256=ut9tY_yEtiBsgQ40u_72Ns87IZHfbMxfnh8t6U9RSGA,16204
47
47
  modal/io_streams.pyi,sha256=aOun_jUFKHSJyUY6-7gKvNoxzcULsa8_hxdtEO7v-gk,13980
@@ -86,7 +86,7 @@ modal/_runtime/container_io_manager.py,sha256=hjkK4gke_A8_mULSfm3F1hZKR0C61lKbRU
86
86
  modal/_runtime/container_io_manager.pyi,sha256=_HvYZzpXX-msFDFuOvk1z6L5DBbv5Dfly16PgYDOojY,23065
87
87
  modal/_runtime/execution_context.py,sha256=73Y5zH_o-MhVCrkJXakYVlFkKqCa2CWvqoHjOfJrJGg,3034
88
88
  modal/_runtime/execution_context.pyi,sha256=IFcW1jphqTchX4fy-45rqfz91RhkZPWtIhIvLvGsNGM,2294
89
- modal/_runtime/gpu_memory_snapshot.py,sha256=HXgqPHQj0LARhmie_h62V95L-M2R1Kg21INUm_IStn8,7574
89
+ modal/_runtime/gpu_memory_snapshot.py,sha256=BWIMKkH-UXTQOJJuXbM15UWCHHSYlJ0XxGlZunKb0Ug,11877
90
90
  modal/_runtime/telemetry.py,sha256=T1RoAGyjBDr1swiM6pPsGRSITm7LI5FDK18oNXxY08U,5163
91
91
  modal/_runtime/user_code_imports.py,sha256=78wJyleqY2RVibqcpbDQyfWVBVT9BjyHPeoV9WdwV5Y,17720
92
92
  modal/_utils/__init__.py,sha256=waLjl5c6IPDhSsdWAm9Bji4e2PVxamYABKAze6CHVXY,28
@@ -151,7 +151,7 @@ modal/experimental/__init__.py,sha256=nuc7AL4r_Fs08DD5dciWFZhrV1nanwoClOfdTcudU0
151
151
  modal/experimental/flash.py,sha256=viXQumCIFp5VFsPFURdFTBTjP_QnsAi8nSWXAMmfjeQ,19744
152
152
  modal/experimental/flash.pyi,sha256=A8_qJGtGoXEzKDdHbvhmCw7oqfneFEvJQK3ZdTOvUdU,10830
153
153
  modal/experimental/ipython.py,sha256=TrCfmol9LGsRZMeDoeMPx3Hv3BFqQhYnmD_iH0pqdhk,2904
154
- modal-1.1.1.dev40.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
154
+ modal-1.1.1.dev44.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
155
155
  modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
156
156
  modal_docs/gen_cli_docs.py,sha256=c1yfBS_x--gL5bs0N4ihMwqwX8l3IBWSkBAKNNIi6bQ,3801
157
157
  modal_docs/gen_reference_docs.py,sha256=d_CQUGQ0rfw28u75I2mov9AlS773z9rG40-yq5o7g2U,6359
@@ -159,10 +159,10 @@ modal_docs/mdmd/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,2
159
159
  modal_docs/mdmd/mdmd.py,sha256=eW5MzrEl7mSclDo4Uv64sQ1-4IyLggldbgUJdBVLDdI,6449
160
160
  modal_docs/mdmd/signatures.py,sha256=XJaZrK7Mdepk5fdX51A8uENiLFNil85Ud0d4MH8H5f0,3218
161
161
  modal_proto/__init__.py,sha256=MIEP8jhXUeGq_eCjYFcqN5b1bxBM4fdk0VESpjWR0fc,28
162
- modal_proto/api.proto,sha256=BhH6QQvIbZ-08MmZUxxK_BuU9718ruM29SX8NGqLV9s,103769
162
+ modal_proto/api.proto,sha256=b9CGi-zi7EqegJKq1UiHLp18DXQF0cDAU9lCky2iOGU,103797
163
163
  modal_proto/api_grpc.py,sha256=1mDcIexGtoVq0675-sqlYVr_M2ncGETpmKsOP7VwE3Y,126369
164
- modal_proto/api_pb2.py,sha256=fk7VQgT6VAuQrP-caTU1Wxx2stTGOGPABD9pHxb_c8E,363956
165
- modal_proto/api_pb2.pyi,sha256=7CzSDCotsAG22rOhA01TOrP1A2Unjo69ypiI_Q1Fzfw,500527
164
+ modal_proto/api_pb2.py,sha256=oW3_AuZmIxZcE9RoFGjr-eCOKJxHeQSL_Fhqsnm339Q,364022
165
+ modal_proto/api_pb2.pyi,sha256=fy34bN5FBEum7zLiMw6PUJkLWjh1VuIE35l8eB9iQRc,500746
166
166
  modal_proto/api_pb2_grpc.py,sha256=tug2WESqUM6l_M8mCkumK0RkDpD_GnMbx-3cH8-Waig,272807
167
167
  modal_proto/api_pb2_grpc.pyi,sha256=w1bj43sKHDFOqZnR28_jWWvkFjSFBcRNY8HLGqKO__g,63991
168
168
  modal_proto/modal_api_grpc.py,sha256=KL5Nw4AS9hJNxfL6VIeuxHz4jIUN7Unz7hYnoSsqyx0,19071
@@ -174,10 +174,10 @@ modal_proto/options_pb2.pyi,sha256=l7DBrbLO7q3Ir-XDkWsajm0d0TQqqrfuX54i4BMpdQg,1
174
174
  modal_proto/options_pb2_grpc.py,sha256=1oboBPFxaTEXt9Aw7EAj8gXHDCNMhZD2VXqocC9l_gk,159
175
175
  modal_proto/options_pb2_grpc.pyi,sha256=CImmhxHsYnF09iENPoe8S4J-n93jtgUYD2JPAc0yJSI,247
176
176
  modal_proto/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
177
- modal_version/__init__.py,sha256=yKVPZgQsYtXUtD_7xH36mRiDGgXyJAA7FA7cDCXtMQI,121
177
+ modal_version/__init__.py,sha256=hR_nl7HUS3EYXDfStfBHO0ndbZEzuHyHLbJvQvXR3_g,121
178
178
  modal_version/__main__.py,sha256=2FO0yYQQwDTh6udt1h-cBnGd1c4ZyHnHSI4BksxzVac,105
179
- modal-1.1.1.dev40.dist-info/METADATA,sha256=G_cZZm2OijpQdzkE97eYr1DIr8doI9Q1_4VSQsl37rA,2460
180
- modal-1.1.1.dev40.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
181
- modal-1.1.1.dev40.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
182
- modal-1.1.1.dev40.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
183
- modal-1.1.1.dev40.dist-info/RECORD,,
179
+ modal-1.1.1.dev44.dist-info/METADATA,sha256=VUIWbFXSw8XKvOrzYBS9rUfJv67fk-axKhZT9FiKS-w,2460
180
+ modal-1.1.1.dev44.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
181
+ modal-1.1.1.dev44.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
182
+ modal-1.1.1.dev44.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
183
+ modal-1.1.1.dev44.dist-info/RECORD,,
modal_proto/api.proto CHANGED
@@ -3008,6 +3008,7 @@ message TaskInfo {
3008
3008
  string gpu_type = 6;
3009
3009
  string sandbox_id = 7;
3010
3010
  TaskSnapshotBehavior snapshot_behavior = 8;
3011
+ GPUConfig gpu_config = 9;
3011
3012
  }
3012
3013
 
3013
3014
  message TaskListRequest {