modAL 1.0.3__tar.gz → 1.0.3.dev0__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.
- {modal-1.0.3 → modal-1.0.3.dev0}/PKG-INFO +2 -2
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_functions.py +2 -3
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_runtime/container_io_manager.py +14 -8
- modal-1.0.3.dev0/modal/_runtime/gpu_memory_snapshot.py +101 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/run.py +0 -12
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/secret.py +4 -43
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/volume.py +1 -6
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/client.pyi +10 -2
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/experimental/__init__.py +4 -6
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/functions.pyi +6 -6
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/image.py +22 -38
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/mount.py +4 -128
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/mount.pyi +0 -22
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/parallel_map.py +23 -47
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/runner.py +7 -2
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/sandbox.py +3 -15
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/sandbox.pyi +6 -14
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/volume.py +49 -17
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/volume.pyi +43 -11
- {modal-1.0.3 → modal-1.0.3.dev0}/modal.egg-info/PKG-INFO +2 -2
- {modal-1.0.3 → modal-1.0.3.dev0}/modal.egg-info/requires.txt +1 -1
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/api.proto +4 -19
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/api_pb2.py +585 -591
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/api_pb2.pyi +6 -32
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_version/__init__.py +1 -1
- {modal-1.0.3 → modal-1.0.3.dev0}/pyproject.toml +1 -1
- modal-1.0.3/modal/_runtime/gpu_memory_snapshot.py +0 -199
- {modal-1.0.3 → modal-1.0.3.dev0}/LICENSE +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/README.md +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/__init__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/__main__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_clustered_functions.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_clustered_functions.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_container_entrypoint.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_ipython.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_location.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_object.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_output.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_partial_function.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_pty.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_resolver.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_resources.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_runtime/__init__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_runtime/asgi.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_runtime/execution_context.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_runtime/telemetry.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_serialization.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_traceback.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_tunnel.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_tunnel.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_type_manager.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/__init__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/app_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/async_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/blob_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/deprecation.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/docker_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/function_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/git_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/hash_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/http_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/logger.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/mount_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/name_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/package_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/shell_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_utils/time_utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_vendor/__init__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_vendor/tblib.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/_watcher.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/app.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/app.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/call_graph.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/__init__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/_download.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/_traceback.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/app.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/cluster.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/config.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/container.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/dict.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/entry_point.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/environment.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/import_refs.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/launch.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/network_file_system.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/profile.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/programs/__init__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/programs/vscode.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/queues.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/token.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cli/utils.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/client.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cls.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/cls.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/config.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/container_process.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/container_process.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/dict.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/dict.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/environments.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/environments.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/exception.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/experimental/ipython.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/file_io.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/file_io.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/file_pattern_matcher.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/functions.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/gpu.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/image.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/io_streams.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/io_streams.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/network_file_system.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/network_file_system.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/object.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/object.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/output.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/parallel_map.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/partial_function.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/partial_function.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/proxy.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/proxy.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/py.typed +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/queue.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/queue.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/requirements/2023.12.312.txt +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/requirements/2023.12.txt +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/requirements/2024.04.txt +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/requirements/2024.10.txt +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/requirements/PREVIEW.txt +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/requirements/README.md +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/requirements/base-images.json +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/retries.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/runner.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/running_app.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/schedule.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/scheduler_placement.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/secret.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/secret.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/serving.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/serving.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/snapshot.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/snapshot.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/stream_type.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/token_flow.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal/token_flow.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal.egg-info/top_level.txt +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_docs/__init__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/__init__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/api_grpc.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/modal_options_grpc.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/options.proto +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/options_grpc.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/options_pb2.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/options_pb2.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_proto/py.typed +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/modal_version/__main__.py +0 -0
- {modal-1.0.3 → modal-1.0.3.dev0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.0.3
|
|
3
|
+
Version: 1.0.3.dev0
|
|
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 @@ Requires-Dist: click~=8.1.0
|
|
|
22
22
|
Requires-Dist: grpclib==0.4.7
|
|
23
23
|
Requires-Dist: protobuf!=4.24.0,<7.0,>=3.19
|
|
24
24
|
Requires-Dist: rich>=12.0.0
|
|
25
|
-
Requires-Dist: synchronicity~=0.9.
|
|
25
|
+
Requires-Dist: synchronicity~=0.9.12
|
|
26
26
|
Requires-Dist: toml
|
|
27
27
|
Requires-Dist: typer>=0.9
|
|
28
28
|
Requires-Dist: types-certifi
|
|
@@ -101,7 +101,6 @@ if TYPE_CHECKING:
|
|
|
101
101
|
|
|
102
102
|
MAX_INTERNAL_FAILURE_COUNT = 8
|
|
103
103
|
|
|
104
|
-
|
|
105
104
|
@dataclasses.dataclass
|
|
106
105
|
class _RetryContext:
|
|
107
106
|
function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType"
|
|
@@ -1662,8 +1661,8 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1662
1661
|
async def spawn(self, *args: P.args, **kwargs: P.kwargs) -> "_FunctionCall[ReturnType]":
|
|
1663
1662
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
1664
1663
|
|
|
1665
|
-
Returns a
|
|
1666
|
-
waited for using
|
|
1664
|
+
Returns a `modal.FunctionCall` object, that can later be polled or
|
|
1665
|
+
waited for using `.get(timeout=...)`.
|
|
1667
1666
|
Conceptually similar to `multiprocessing.pool.apply_async`, or a Future/Promise in other contexts.
|
|
1668
1667
|
"""
|
|
1669
1668
|
self._check_no_web_url("spawn")
|
|
@@ -323,7 +323,6 @@ class _ContainerIOManager:
|
|
|
323
323
|
self._heartbeat_loop = None
|
|
324
324
|
self._heartbeat_condition = None
|
|
325
325
|
self._waiting_for_memory_snapshot = False
|
|
326
|
-
self._cuda_checkpoint_session = None
|
|
327
326
|
|
|
328
327
|
self._is_interactivity_enabled = False
|
|
329
328
|
self._fetching_inputs = True
|
|
@@ -882,11 +881,13 @@ class _ContainerIOManager:
|
|
|
882
881
|
# Restore GPU memory.
|
|
883
882
|
if self.function_def._experimental_enable_gpu_snapshot and self.function_def.resources.gpu_config.gpu_type:
|
|
884
883
|
logger.debug("GPU memory snapshot enabled. Attempting to restore GPU memory.")
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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()
|
|
890
891
|
|
|
891
892
|
# Restore input to default state.
|
|
892
893
|
self.current_input_id = None
|
|
@@ -906,9 +907,14 @@ class _ContainerIOManager:
|
|
|
906
907
|
# Snapshot GPU memory.
|
|
907
908
|
if self.function_def._experimental_enable_gpu_snapshot and self.function_def.resources.gpu_config.gpu_type:
|
|
908
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
|
+
f"Cannot snapshot GPU state if it isn't running. Current GPU state: {gpu_process_state}"
|
|
914
|
+
)
|
|
909
915
|
|
|
910
|
-
|
|
911
|
-
|
|
916
|
+
gpu_memory_snapshot.toggle()
|
|
917
|
+
gpu_memory_snapshot.wait_for_state(gpu_memory_snapshot.CudaCheckpointState.CHECKPOINTED)
|
|
912
918
|
|
|
913
919
|
# Notify the heartbeat loop that the snapshot phase has begun in order to
|
|
914
920
|
# prevent it from sending heartbeat RPCs
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
subprocess.run(
|
|
40
|
+
[
|
|
41
|
+
CUDA_CHECKPOINT_PATH,
|
|
42
|
+
"--toggle",
|
|
43
|
+
"--pid",
|
|
44
|
+
str(pid),
|
|
45
|
+
],
|
|
46
|
+
check=True,
|
|
47
|
+
capture_output=True,
|
|
48
|
+
text=True,
|
|
49
|
+
)
|
|
50
|
+
logger.debug("Successfully toggled CUDA checkpoint state")
|
|
51
|
+
|
|
52
|
+
except subprocess.CalledProcessError as e:
|
|
53
|
+
logger.debug(f"Failed to toggle CUDA checkpoint state: {e.stderr}")
|
|
54
|
+
raise CudaCheckpointException(e.stderr)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_state() -> CudaCheckpointState:
|
|
58
|
+
"""Get current CUDA checkpoint state for this process."""
|
|
59
|
+
pid = get_own_pid()
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
result = subprocess.run(
|
|
63
|
+
[CUDA_CHECKPOINT_PATH, "--get-state", "--pid", str(pid)], check=True, capture_output=True, text=True
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Parse output to get state
|
|
67
|
+
state_str = result.stdout.strip().lower()
|
|
68
|
+
return CudaCheckpointState(state_str)
|
|
69
|
+
|
|
70
|
+
except subprocess.CalledProcessError as e:
|
|
71
|
+
logger.debug(f"Failed to get CUDA checkpoint state: {e.stderr}")
|
|
72
|
+
raise CudaCheckpointException(e.stderr)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def wait_for_state(target_state: CudaCheckpointState, timeout_secs: float = 5.0):
|
|
76
|
+
"""Wait for CUDA checkpoint to reach a specific state."""
|
|
77
|
+
logger.debug(f"Waiting for CUDA checkpoint state {target_state.value}")
|
|
78
|
+
start_time = time.monotonic()
|
|
79
|
+
|
|
80
|
+
while True:
|
|
81
|
+
current_state = get_state()
|
|
82
|
+
|
|
83
|
+
if current_state == target_state:
|
|
84
|
+
logger.debug(f"Target state {target_state.value} reached")
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
if current_state == CudaCheckpointState.FAILED:
|
|
88
|
+
raise CudaCheckpointException(f"CUDA process state is {current_state}")
|
|
89
|
+
|
|
90
|
+
elapsed = time.monotonic() - start_time
|
|
91
|
+
if elapsed >= timeout_secs:
|
|
92
|
+
raise CudaCheckpointException(f"Timeout after {elapsed:.2f}s waiting for state {target_state.value}")
|
|
93
|
+
|
|
94
|
+
time.sleep(0.1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_own_pid():
|
|
98
|
+
"""Returns the Process ID (PID) of the current Python process
|
|
99
|
+
using only the standard library.
|
|
100
|
+
"""
|
|
101
|
+
return os.getpid()
|
|
@@ -171,14 +171,6 @@ def _write_local_result(result_path: str, res: Any):
|
|
|
171
171
|
fid.write(res)
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
def _validate_interactive_quiet_params(ctx):
|
|
175
|
-
interactive = ctx.obj["interactive"]
|
|
176
|
-
show_progress = ctx.obj["show_progress"]
|
|
177
|
-
|
|
178
|
-
if not show_progress and interactive:
|
|
179
|
-
raise InvalidError("To use interactive mode, remove the --quiet flag")
|
|
180
|
-
|
|
181
|
-
|
|
182
174
|
def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[tuple[str, ...], dict[str, Any]], Any]):
|
|
183
175
|
@click.pass_context
|
|
184
176
|
def f(ctx, **kwargs):
|
|
@@ -188,8 +180,6 @@ def _make_click_function(app, signature: CliRunnableSignature, inner: Callable[[
|
|
|
188
180
|
else:
|
|
189
181
|
args = ()
|
|
190
182
|
|
|
191
|
-
_validate_interactive_quiet_params(ctx)
|
|
192
|
-
|
|
193
183
|
show_progress: bool = ctx.obj["show_progress"]
|
|
194
184
|
with enable_output(show_progress):
|
|
195
185
|
with run_app(
|
|
@@ -301,8 +291,6 @@ def _get_click_command_for_local_entrypoint(app: App, entrypoint: LocalEntrypoin
|
|
|
301
291
|
assert len(args) == 0 and len(kwargs) == 0
|
|
302
292
|
args = ctx.args
|
|
303
293
|
|
|
304
|
-
_validate_interactive_quiet_params(ctx)
|
|
305
|
-
|
|
306
294
|
show_progress: bool = ctx.obj["show_progress"]
|
|
307
295
|
with enable_output(show_progress):
|
|
308
296
|
with run_app(
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# Copyright Modal Labs 2022
|
|
2
|
-
import json
|
|
3
2
|
import os
|
|
4
3
|
import platform
|
|
5
4
|
import subprocess
|
|
6
|
-
from pathlib import Path
|
|
7
5
|
from tempfile import NamedTemporaryFile
|
|
8
6
|
from typing import Optional
|
|
9
7
|
|
|
@@ -50,17 +48,14 @@ async def list_(env: Optional[str] = ENV_OPTION, json: bool = False):
|
|
|
50
48
|
@secret_cli.command("create", help="Create a new secret.")
|
|
51
49
|
@synchronizer.create_blocking
|
|
52
50
|
async def create(
|
|
53
|
-
secret_name
|
|
54
|
-
keyvalues:
|
|
51
|
+
secret_name,
|
|
52
|
+
keyvalues: list[str] = typer.Argument(..., help="Space-separated KEY=VALUE items"),
|
|
55
53
|
env: Optional[str] = ENV_OPTION,
|
|
56
|
-
from_dotenv: Optional[Path] = typer.Option(default=None, help="Path to a .env file to load secrets from."),
|
|
57
|
-
from_json: Optional[Path] = typer.Option(default=None, help="Path to a JSON file to load secrets from."),
|
|
58
54
|
force: bool = typer.Option(False, "--force", help="Overwrite the secret if it already exists."),
|
|
59
55
|
):
|
|
60
56
|
env = ensure_env(env)
|
|
61
57
|
env_dict = {}
|
|
62
|
-
|
|
63
|
-
for arg in keyvalues or []:
|
|
58
|
+
for arg in keyvalues:
|
|
64
59
|
if "=" in arg:
|
|
65
60
|
key, value = arg.split("=", 1)
|
|
66
61
|
if value == "-":
|
|
@@ -68,51 +63,17 @@ async def create(
|
|
|
68
63
|
env_dict[key] = value
|
|
69
64
|
else:
|
|
70
65
|
raise click.UsageError(
|
|
71
|
-
"""Each item should be of the form <KEY>=VALUE. To enter secrets using your $EDITOR, use `<KEY>=-`.
|
|
72
|
-
enter secrets from environment variables, use `<KEY>="$ENV_VAR"`.
|
|
66
|
+
"""Each item should be of the form <KEY>=VALUE. To enter secrets using your $EDITOR, use `<KEY>=-`.
|
|
73
67
|
|
|
74
68
|
E.g.
|
|
75
69
|
|
|
76
70
|
modal secret create my-credentials username=john password=-
|
|
77
|
-
modal secret create my-credentials username=john password="$PASSWORD"
|
|
78
71
|
"""
|
|
79
72
|
)
|
|
80
73
|
|
|
81
|
-
if from_dotenv:
|
|
82
|
-
if not from_dotenv.is_file():
|
|
83
|
-
raise click.UsageError(f"Could not read .env file at {from_dotenv}")
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
from dotenv import dotenv_values
|
|
87
|
-
except ImportError:
|
|
88
|
-
raise ImportError(
|
|
89
|
-
"Need the `python-dotenv` package installed. You can install it by running `pip install python-dotenv`."
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
try:
|
|
93
|
-
env_dict.update(dotenv_values(from_dotenv))
|
|
94
|
-
except Exception as e:
|
|
95
|
-
raise click.UsageError(f"Could not parse .env file at {from_dotenv}: {e}")
|
|
96
|
-
|
|
97
|
-
if from_json:
|
|
98
|
-
if not from_json.is_file():
|
|
99
|
-
raise click.UsageError(f"Could not read JSON file at {from_json}")
|
|
100
|
-
|
|
101
|
-
try:
|
|
102
|
-
with from_json.open("r") as f:
|
|
103
|
-
env_dict.update(json.load(f))
|
|
104
|
-
except Exception as e:
|
|
105
|
-
raise click.UsageError(f"Could not parse JSON file at {from_json}: {e}")
|
|
106
|
-
|
|
107
74
|
if not env_dict:
|
|
108
75
|
raise click.UsageError("You need to specify at least one key for your secret")
|
|
109
76
|
|
|
110
|
-
for k, v in env_dict.items():
|
|
111
|
-
if not isinstance(k, str) or not k:
|
|
112
|
-
raise click.UsageError(f"Invalid key: '{k}'")
|
|
113
|
-
if not isinstance(v, str):
|
|
114
|
-
raise click.UsageError(f"Non-string value for secret '{k}'")
|
|
115
|
-
|
|
116
77
|
# Create secret
|
|
117
78
|
await _Secret.create_deployed(secret_name, env_dict, overwrite=force)
|
|
118
79
|
|
|
@@ -154,10 +154,6 @@ async def ls(
|
|
|
154
154
|
filetype = "dir"
|
|
155
155
|
elif entry.type == api_pb2.FileEntry.FileType.SYMLINK:
|
|
156
156
|
filetype = "link"
|
|
157
|
-
elif entry.type == api_pb2.FileEntry.FileType.FIFO:
|
|
158
|
-
filetype = "fifo"
|
|
159
|
-
elif entry.type == api_pb2.FileEntry.FileType.SOCKET:
|
|
160
|
-
filetype = "socket"
|
|
161
157
|
else:
|
|
162
158
|
filetype = "file"
|
|
163
159
|
rows.append(
|
|
@@ -265,13 +261,12 @@ async def rm(
|
|
|
265
261
|
async def cp(
|
|
266
262
|
volume_name: str,
|
|
267
263
|
paths: list[str], # accepts multiple paths, last path is treated as destination path
|
|
268
|
-
recursive: bool = Option(False, "-r", "--recursive", help="Copy directories recursively"),
|
|
269
264
|
env: Optional[str] = ENV_OPTION,
|
|
270
265
|
):
|
|
271
266
|
ensure_env(env)
|
|
272
267
|
volume = _Volume.from_name(volume_name, environment_name=env)
|
|
273
268
|
*src_paths, dst_path = paths
|
|
274
|
-
await volume.copy_files(src_paths, dst_path
|
|
269
|
+
await volume.copy_files(src_paths, dst_path)
|
|
275
270
|
|
|
276
271
|
|
|
277
272
|
@volume_cli.command(
|
|
@@ -27,7 +27,11 @@ class _Client:
|
|
|
27
27
|
_snapshotted: bool
|
|
28
28
|
|
|
29
29
|
def __init__(
|
|
30
|
-
self,
|
|
30
|
+
self,
|
|
31
|
+
server_url: str,
|
|
32
|
+
client_type: int,
|
|
33
|
+
credentials: typing.Optional[tuple[str, str]],
|
|
34
|
+
version: str = "1.0.3.dev0",
|
|
31
35
|
): ...
|
|
32
36
|
def is_closed(self) -> bool: ...
|
|
33
37
|
@property
|
|
@@ -86,7 +90,11 @@ class Client:
|
|
|
86
90
|
_snapshotted: bool
|
|
87
91
|
|
|
88
92
|
def __init__(
|
|
89
|
-
self,
|
|
93
|
+
self,
|
|
94
|
+
server_url: str,
|
|
95
|
+
client_type: int,
|
|
96
|
+
credentials: typing.Optional[tuple[str, str]],
|
|
97
|
+
version: str = "1.0.3.dev0",
|
|
90
98
|
): ...
|
|
91
99
|
def is_closed(self) -> bool: ...
|
|
92
100
|
@property
|
|
@@ -94,9 +94,8 @@ async def raw_dockerfile_image(
|
|
|
94
94
|
|
|
95
95
|
Unlike for `modal.Image.from_dockerfile`, the provided recipe will not be embellished with
|
|
96
96
|
steps to install dependencies for the Modal client package. As a consequence, the resulting
|
|
97
|
-
Image cannot be used with a modal Function unless those dependencies are
|
|
98
|
-
|
|
99
|
-
directly used with a modal Sandbox, which does not need the Modal client.
|
|
97
|
+
Image cannot be used with a modal Function unless those dependencies are added in a subsequent
|
|
98
|
+
layer. It _can_ be directly used with a modal Sandbox, which does not need the Modal client.
|
|
100
99
|
|
|
101
100
|
We expect to support this experimental function until the `2025.04` Modal Image Builder is
|
|
102
101
|
stable, at which point Modal Image recipes will no longer install the client dependencies
|
|
@@ -128,9 +127,8 @@ async def raw_registry_image(
|
|
|
128
127
|
|
|
129
128
|
Unlike for `modal.Image.from_registry`, the provided recipe will not be embellished with
|
|
130
129
|
steps to install dependencies for the Modal client package. As a consequence, the resulting
|
|
131
|
-
Image cannot be used with a modal Function unless those dependencies are
|
|
132
|
-
|
|
133
|
-
directly used with a modal Sandbox, which does not need the Modal client.
|
|
130
|
+
Image cannot be used with a modal Function unless those dependencies are added in a subsequent
|
|
131
|
+
layer. It _can_ be directly used with a modal Sandbox, which does not need the Modal client.
|
|
134
132
|
|
|
135
133
|
We expect to support this experimental function until the `2025.04` Modal Image Builder is
|
|
136
134
|
stable, at which point Modal Image recipes will no longer install the client dependencies
|
|
@@ -227,11 +227,11 @@ class Function(
|
|
|
227
227
|
|
|
228
228
|
_call_generator: ___call_generator_spec[typing_extensions.Self]
|
|
229
229
|
|
|
230
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
230
|
+
class __remote_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
|
231
231
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
|
232
232
|
async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER: ...
|
|
233
233
|
|
|
234
|
-
remote: __remote_spec[modal._functions.
|
|
234
|
+
remote: __remote_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
|
235
235
|
|
|
236
236
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
237
237
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
|
@@ -246,12 +246,12 @@ class Function(
|
|
|
246
246
|
self, *args: modal._functions.P.args, **kwargs: modal._functions.P.kwargs
|
|
247
247
|
) -> modal._functions.OriginalReturnType: ...
|
|
248
248
|
|
|
249
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
249
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
|
250
250
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
|
251
251
|
async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
|
252
252
|
|
|
253
253
|
_experimental_spawn: ___experimental_spawn_spec[
|
|
254
|
-
modal._functions.
|
|
254
|
+
modal._functions.ReturnType, modal._functions.P, typing_extensions.Self
|
|
255
255
|
]
|
|
256
256
|
|
|
257
257
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
|
|
@@ -260,11 +260,11 @@ class Function(
|
|
|
260
260
|
|
|
261
261
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
|
|
262
262
|
|
|
263
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
263
|
+
class __spawn_spec(typing_extensions.Protocol[ReturnType_INNER, P_INNER, SUPERSELF]):
|
|
264
264
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
|
265
265
|
async def aio(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]: ...
|
|
266
266
|
|
|
267
|
-
spawn: __spawn_spec[modal._functions.
|
|
267
|
+
spawn: __spawn_spec[modal._functions.ReturnType, modal._functions.P, typing_extensions.Self]
|
|
268
268
|
|
|
269
269
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]: ...
|
|
270
270
|
|
|
@@ -647,13 +647,7 @@ class _Image(_Object, type_prefix="im"):
|
|
|
647
647
|
metadata = resp.metadata
|
|
648
648
|
|
|
649
649
|
if result.status == api_pb2.GenericResult.GENERIC_STATUS_FAILURE:
|
|
650
|
-
|
|
651
|
-
raise RemoteError(f"Image build for {image_id} failed with the exception:\n{result.exception}")
|
|
652
|
-
else:
|
|
653
|
-
msg = f"Image build for {image_id} failed. See build logs for more details."
|
|
654
|
-
if not _get_output_manager():
|
|
655
|
-
msg += " (Hint: Use `modal.enable_output()` to see logs from the process building the Image.)"
|
|
656
|
-
raise RemoteError(msg)
|
|
650
|
+
raise RemoteError(f"Image build for {image_id} failed with the exception:\n{result.exception}")
|
|
657
651
|
elif result.status == api_pb2.GenericResult.GENERIC_STATUS_TERMINATED:
|
|
658
652
|
raise RemoteError(f"Image build for {image_id} terminated due to external shut-down. Please try again.")
|
|
659
653
|
elif result.status == api_pb2.GenericResult.GENERIC_STATUS_TIMEOUT:
|
|
@@ -1528,14 +1522,12 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1528
1522
|
modal_requirements_commands = []
|
|
1529
1523
|
if builder_version <= "2024.10":
|
|
1530
1524
|
# past 2024.10, client dependencies are mounted at runtime
|
|
1531
|
-
modal_requirements_commands.extend(
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
)
|
|
1538
|
-
if "2024.10" >= builder_version > "2023.12":
|
|
1525
|
+
modal_requirements_commands.extend([
|
|
1526
|
+
f"COPY {CONTAINER_REQUIREMENTS_PATH} {CONTAINER_REQUIREMENTS_PATH}",
|
|
1527
|
+
f"RUN python -m pip install --upgrade {_base_image_config('package_tools', builder_version)}",
|
|
1528
|
+
f"RUN {requirements_prefix}{_get_modal_requirements_command(builder_version)}",
|
|
1529
|
+
])
|
|
1530
|
+
if "2024.10" >= builder_version > "2023.12":
|
|
1539
1531
|
modal_requirements_commands.append(f"RUN rm {CONTAINER_REQUIREMENTS_PATH}")
|
|
1540
1532
|
|
|
1541
1533
|
return [
|
|
@@ -1838,31 +1830,23 @@ class _Image(_Object, type_prefix="im"):
|
|
|
1838
1830
|
f"FROM python:{full_python_version}-slim-{debian_codename}",
|
|
1839
1831
|
]
|
|
1840
1832
|
if version <= "2024.10":
|
|
1841
|
-
commands.extend(
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
"RUN apt-get install -y gcc gfortran build-essential",
|
|
1850
|
-
f"RUN pip install --upgrade {_base_image_config('package_tools', version)}",
|
|
1851
|
-
]
|
|
1852
|
-
)
|
|
1833
|
+
commands.extend([
|
|
1834
|
+
f"COPY {CONTAINER_REQUIREMENTS_PATH} {CONTAINER_REQUIREMENTS_PATH}",
|
|
1835
|
+
])
|
|
1836
|
+
commands.extend([
|
|
1837
|
+
"RUN apt-get update",
|
|
1838
|
+
"RUN apt-get install -y gcc gfortran build-essential",
|
|
1839
|
+
f"RUN pip install --upgrade {_base_image_config('package_tools', version)}",
|
|
1840
|
+
])
|
|
1853
1841
|
if version <= "2024.10":
|
|
1854
1842
|
# after 2024.10, modal requirements are mounted at runtime
|
|
1855
|
-
commands.extend(
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
# Set debian front-end to non-interactive to avoid users getting stuck with input prompts.
|
|
1863
|
-
"RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections",
|
|
1864
|
-
]
|
|
1865
|
-
)
|
|
1843
|
+
commands.extend([
|
|
1844
|
+
f"RUN {_get_modal_requirements_command(version)}",
|
|
1845
|
+
])
|
|
1846
|
+
commands.extend([
|
|
1847
|
+
# Set debian front-end to non-interactive to avoid users getting stuck with input prompts.
|
|
1848
|
+
"RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections",
|
|
1849
|
+
])
|
|
1866
1850
|
if "2024.10" >= version > "2023.12":
|
|
1867
1851
|
commands.append(f"RUN rm {CONTAINER_REQUIREMENTS_PATH}")
|
|
1868
1852
|
if version > "2024.10":
|
|
@@ -21,7 +21,7 @@ from modal_version import __version__
|
|
|
21
21
|
|
|
22
22
|
from ._object import _get_environment_name, _Object
|
|
23
23
|
from ._resolver import Resolver
|
|
24
|
-
from ._utils.async_utils import
|
|
24
|
+
from ._utils.async_utils import aclosing, async_map, synchronize_api
|
|
25
25
|
from ._utils.blob_utils import FileUploadSpec, blob_upload_file, get_file_upload_spec_from_path
|
|
26
26
|
from ._utils.deprecation import deprecation_warning
|
|
27
27
|
from ._utils.grpc_utils import retry_transient_errors
|
|
@@ -115,8 +115,7 @@ class _MountFile(_MountEntry):
|
|
|
115
115
|
def get_files_to_upload(self):
|
|
116
116
|
local_file = self.local_file.resolve()
|
|
117
117
|
if not local_file.exists():
|
|
118
|
-
|
|
119
|
-
raise FileNotFoundError(msg)
|
|
118
|
+
raise FileNotFoundError(local_file)
|
|
120
119
|
|
|
121
120
|
rel_filename = self.remote_path
|
|
122
121
|
yield local_file, rel_filename
|
|
@@ -145,12 +144,10 @@ class _MountDir(_MountEntry):
|
|
|
145
144
|
local_dir = self.local_dir.expanduser().absolute()
|
|
146
145
|
|
|
147
146
|
if not local_dir.exists():
|
|
148
|
-
|
|
149
|
-
raise FileNotFoundError(msg)
|
|
147
|
+
raise FileNotFoundError(local_dir)
|
|
150
148
|
|
|
151
149
|
if not local_dir.is_dir():
|
|
152
|
-
|
|
153
|
-
raise NotADirectoryError(msg)
|
|
150
|
+
raise NotADirectoryError(local_dir)
|
|
154
151
|
|
|
155
152
|
if self.recursive:
|
|
156
153
|
gen = (os.path.join(root, name) for root, dirs, files in os.walk(local_dir) for name in files)
|
|
@@ -813,124 +810,3 @@ def _is_modal_path(remote_path: PurePosixPath):
|
|
|
813
810
|
if is_modal_path:
|
|
814
811
|
return True
|
|
815
812
|
return False
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
REMOTE_PACKAGES_PATH = "/__modal/deps"
|
|
819
|
-
REMOTE_SITECUSTOMIZE_PATH = "/pkg/sitecustomize.py"
|
|
820
|
-
|
|
821
|
-
SITECUSTOMIZE_CONTENT = f"""
|
|
822
|
-
# This file is automatically generated by Modal.
|
|
823
|
-
# It ensures that Modal's python dependencies are available in the Python PATH,
|
|
824
|
-
# while prioritizing user-installed packages.
|
|
825
|
-
import sys; sys.path.append('{REMOTE_PACKAGES_PATH}')
|
|
826
|
-
""".strip()
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
async def _create_single_mount(
|
|
830
|
-
client: _Client,
|
|
831
|
-
builder_version: str,
|
|
832
|
-
python_version: str,
|
|
833
|
-
platform: str,
|
|
834
|
-
arch: str,
|
|
835
|
-
uv_python_platform: str = None,
|
|
836
|
-
check_if_exists: bool = True,
|
|
837
|
-
):
|
|
838
|
-
import subprocess
|
|
839
|
-
import tempfile
|
|
840
|
-
|
|
841
|
-
profile_environment = config.get("environment")
|
|
842
|
-
abi_tag = "cp" + python_version.replace(".", "")
|
|
843
|
-
mount_name = f"{builder_version}-{abi_tag}-{platform}-{arch}"
|
|
844
|
-
uv_python_platform = uv_python_platform or f"{arch}-{platform}"
|
|
845
|
-
|
|
846
|
-
if check_if_exists:
|
|
847
|
-
try:
|
|
848
|
-
await Mount.from_name(mount_name, namespace=api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL).hydrate.aio(client)
|
|
849
|
-
print(f"✅ Found existing mount {mount_name} in global namespace.")
|
|
850
|
-
return
|
|
851
|
-
except modal.exception.NotFoundError:
|
|
852
|
-
pass
|
|
853
|
-
|
|
854
|
-
with tempfile.TemporaryDirectory() as tmpd:
|
|
855
|
-
print(f"📦 Building {mount_name}.")
|
|
856
|
-
requirements = os.path.join(os.path.dirname(__file__), f"requirements/{builder_version}.txt")
|
|
857
|
-
subprocess.run(
|
|
858
|
-
[
|
|
859
|
-
"uv",
|
|
860
|
-
"pip",
|
|
861
|
-
"install",
|
|
862
|
-
"--strict",
|
|
863
|
-
"--no-deps",
|
|
864
|
-
"--no-cache",
|
|
865
|
-
"-r",
|
|
866
|
-
requirements,
|
|
867
|
-
"--compile-bytecode",
|
|
868
|
-
"--target",
|
|
869
|
-
tmpd,
|
|
870
|
-
"--python-platform",
|
|
871
|
-
uv_python_platform,
|
|
872
|
-
"--python-version",
|
|
873
|
-
python_version,
|
|
874
|
-
],
|
|
875
|
-
check=True,
|
|
876
|
-
capture_output=True,
|
|
877
|
-
)
|
|
878
|
-
|
|
879
|
-
print(f"🌐 Downloaded and unpacked packages to {tmpd}.")
|
|
880
|
-
|
|
881
|
-
python_mount = Mount._from_local_dir(tmpd, remote_path=REMOTE_PACKAGES_PATH)
|
|
882
|
-
|
|
883
|
-
with tempfile.NamedTemporaryFile() as sitecustomize:
|
|
884
|
-
sitecustomize.write(
|
|
885
|
-
SITECUSTOMIZE_CONTENT.encode("utf-8"),
|
|
886
|
-
)
|
|
887
|
-
sitecustomize.flush()
|
|
888
|
-
|
|
889
|
-
python_mount = python_mount.add_local_file(
|
|
890
|
-
sitecustomize.name,
|
|
891
|
-
remote_path=REMOTE_SITECUSTOMIZE_PATH,
|
|
892
|
-
)
|
|
893
|
-
|
|
894
|
-
await python_mount._deploy.aio(
|
|
895
|
-
mount_name,
|
|
896
|
-
api_pb2.DEPLOYMENT_NAMESPACE_GLOBAL,
|
|
897
|
-
environment_name=profile_environment,
|
|
898
|
-
client=client,
|
|
899
|
-
)
|
|
900
|
-
print(f"✅ Deployed mount {mount_name} to global namespace.")
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
async def _create_client_dependency_mounts(
|
|
904
|
-
client=None,
|
|
905
|
-
check_if_exists=True,
|
|
906
|
-
python_versions: list[str] = list(PYTHON_STANDALONE_VERSIONS),
|
|
907
|
-
):
|
|
908
|
-
coros = []
|
|
909
|
-
for python_version in python_versions:
|
|
910
|
-
# glibc >= 2.17
|
|
911
|
-
coros.append(
|
|
912
|
-
_create_single_mount(
|
|
913
|
-
client,
|
|
914
|
-
"PREVIEW",
|
|
915
|
-
python_version,
|
|
916
|
-
"manylinux_2_17",
|
|
917
|
-
"x86_64",
|
|
918
|
-
check_if_exists=check_if_exists,
|
|
919
|
-
)
|
|
920
|
-
)
|
|
921
|
-
# musl >= 1.2
|
|
922
|
-
coros.append(
|
|
923
|
-
_create_single_mount(
|
|
924
|
-
client,
|
|
925
|
-
"PREVIEW",
|
|
926
|
-
python_version,
|
|
927
|
-
"musllinux_1_2",
|
|
928
|
-
"x86_64",
|
|
929
|
-
uv_python_platform="x86_64-unknown-linux-musl",
|
|
930
|
-
check_if_exists=check_if_exists,
|
|
931
|
-
)
|
|
932
|
-
)
|
|
933
|
-
await TaskContext.gather(*coros)
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
create_client_dependency_mounts = synchronize_api(_create_client_dependency_mounts)
|