modal 0.73.126__tar.gz → 0.73.128__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-0.73.126 → modal-0.73.128}/PKG-INFO +1 -1
- {modal-0.73.126 → modal-0.73.128}/modal/__init__.py +2 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_functions.py +4 -2
- {modal-0.73.126 → modal-0.73.128}/modal/_partial_function.py +54 -0
- {modal-0.73.126 → modal-0.73.128}/modal/app.py +34 -5
- {modal-0.73.126 → modal-0.73.128}/modal/app.pyi +3 -2
- {modal-0.73.126 → modal-0.73.128}/modal/client.pyi +2 -2
- {modal-0.73.126 → modal-0.73.128}/modal/functions.pyi +2 -1
- {modal-0.73.126 → modal-0.73.128}/modal/partial_function.py +2 -0
- {modal-0.73.126 → modal-0.73.128}/modal/partial_function.pyi +9 -0
- {modal-0.73.126 → modal-0.73.128}/modal/sandbox.py +5 -1
- {modal-0.73.126 → modal-0.73.128}/modal.egg-info/PKG-INFO +1 -1
- {modal-0.73.126 → modal-0.73.128}/modal_proto/api.proto +2 -2
- {modal-0.73.126 → modal-0.73.128}/modal_proto/api_pb2.py +1 -1
- {modal-0.73.126 → modal-0.73.128}/modal_version/_version_generated.py +1 -1
- {modal-0.73.126 → modal-0.73.128}/LICENSE +0 -0
- {modal-0.73.126 → modal-0.73.128}/README.md +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/__main__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_clustered_functions.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_clustered_functions.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_container_entrypoint.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_ipython.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_location.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_object.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_output.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_proxy_tunnel.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_pty.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_resolver.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_resources.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_runtime/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_runtime/asgi.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_runtime/container_io_manager.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_runtime/execution_context.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_runtime/execution_context.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_runtime/telemetry.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_runtime/user_code_imports.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_serialization.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_traceback.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_tunnel.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_tunnel.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/app_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/async_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/blob_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/deprecation.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/docker_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/function_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/git_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/grpc_testing.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/grpc_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/hash_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/http_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/jwt_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/logger.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/mount_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/name_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/package_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/pattern_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_utils/shell_utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_vendor/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_vendor/cloudpickle.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_vendor/tblib.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/_watcher.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/call_graph.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/_download.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/_traceback.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/app.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/config.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/container.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/dict.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/entry_point.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/environment.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/import_refs.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/launch.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/network_file_system.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/profile.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/programs/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/programs/vscode.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/queues.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/run.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/secret.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/token.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/utils.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cli/volume.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/client.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cloud_bucket_mount.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cls.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/cls.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/config.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/container_process.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/container_process.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/dict.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/dict.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/environments.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/environments.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/exception.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/experimental.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/experimental.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/extensions/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/extensions/ipython.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/file_io.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/file_io.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/file_pattern_matcher.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/functions.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/gpu.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/image.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/image.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/io_streams.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/io_streams.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/mount.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/mount.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/network_file_system.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/network_file_system.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/object.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/object.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/output.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/parallel_map.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/parallel_map.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/proxy.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/proxy.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/py.typed +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/queue.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/queue.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/requirements/2023.12.312.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/requirements/2023.12.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/requirements/2024.04.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/requirements/2024.10.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/requirements/PREVIEW.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/requirements/README.md +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/requirements/base-images.json +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/retries.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/runner.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/runner.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/running_app.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/sandbox.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/schedule.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/scheduler_placement.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/secret.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/secret.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/serving.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/serving.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/snapshot.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/snapshot.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/stream_type.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/token_flow.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/token_flow.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/volume.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal/volume.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal.egg-info/SOURCES.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal.egg-info/dependency_links.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal.egg-info/entry_points.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal.egg-info/requires.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal.egg-info/top_level.txt +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_docs/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_docs/gen_cli_docs.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_docs/gen_reference_docs.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_docs/mdmd/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_docs/mdmd/signatures.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/api_grpc.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/api_pb2.pyi +6 -6
- {modal-0.73.126 → modal-0.73.128}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/modal_api_grpc.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/modal_options_grpc.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/options.proto +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/options_grpc.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/options_pb2.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/options_pb2.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_proto/py.typed +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_version/__init__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/modal_version/__main__.py +0 -0
- {modal-0.73.126 → modal-0.73.128}/pyproject.toml +0 -0
- {modal-0.73.126 → modal-0.73.128}/setup.cfg +0 -0
@@ -27,6 +27,7 @@ try:
|
|
27
27
|
asgi_app,
|
28
28
|
batched,
|
29
29
|
build,
|
30
|
+
concurrent,
|
30
31
|
enter,
|
31
32
|
exit,
|
32
33
|
fastapi_endpoint,
|
@@ -82,6 +83,7 @@ __all__ = [
|
|
82
83
|
"asgi_app",
|
83
84
|
"batched",
|
84
85
|
"build",
|
86
|
+
"concurrent",
|
85
87
|
"current_function_call_id",
|
86
88
|
"current_input_id",
|
87
89
|
"enable_output",
|
@@ -435,7 +435,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
435
435
|
max_containers: Optional[int] = None,
|
436
436
|
buffer_containers: Optional[int] = None,
|
437
437
|
scaledown_window: Optional[int] = None,
|
438
|
-
|
438
|
+
max_concurrent_inputs: Optional[int] = None,
|
439
|
+
target_concurrent_inputs: Optional[int] = None,
|
439
440
|
batch_max_size: Optional[int] = None,
|
440
441
|
batch_wait_ms: Optional[int] = None,
|
441
442
|
cloud: Optional[str] = None,
|
@@ -786,7 +787,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
786
787
|
runtime_perf_record=config.get("runtime_perf_record"),
|
787
788
|
app_name=app_name,
|
788
789
|
is_builder_function=is_builder_function,
|
789
|
-
|
790
|
+
max_concurrent_inputs=max_concurrent_inputs or 0,
|
791
|
+
target_concurrent_inputs=target_concurrent_inputs or 0,
|
790
792
|
batch_max_size=batch_max_size or 0,
|
791
793
|
batch_linger_ms=batch_wait_ms or 0,
|
792
794
|
worker_id=config.get("worker_id"),
|
@@ -59,6 +59,8 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
|
|
59
59
|
force_build: bool
|
60
60
|
cluster_size: Optional[int] # Experimental: Clustered functions
|
61
61
|
build_timeout: Optional[int]
|
62
|
+
max_concurrent_inputs: Optional[int]
|
63
|
+
target_concurrent_inputs: Optional[int]
|
62
64
|
|
63
65
|
def __init__(
|
64
66
|
self,
|
@@ -72,6 +74,8 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
|
|
72
74
|
cluster_size: Optional[int] = None, # Experimental: Clustered functions
|
73
75
|
force_build: bool = False,
|
74
76
|
build_timeout: Optional[int] = None,
|
77
|
+
max_concurrent_inputs: Optional[int] = None,
|
78
|
+
target_concurrent_inputs: Optional[int] = None,
|
75
79
|
):
|
76
80
|
self.raw_f = raw_f
|
77
81
|
self.flags = flags
|
@@ -89,6 +93,8 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
|
|
89
93
|
self.cluster_size = cluster_size # Experimental: Clustered functions
|
90
94
|
self.force_build = force_build
|
91
95
|
self.build_timeout = build_timeout
|
96
|
+
self.max_concurrent_inputs = max_concurrent_inputs
|
97
|
+
self.target_concurrent_inputs = target_concurrent_inputs
|
92
98
|
|
93
99
|
def _get_raw_f(self) -> Callable[P, ReturnType]:
|
94
100
|
return self.raw_f
|
@@ -143,6 +149,8 @@ class _PartialFunction(typing.Generic[P, ReturnType, OriginalReturnType]):
|
|
143
149
|
batch_wait_ms=self.batch_wait_ms,
|
144
150
|
force_build=self.force_build,
|
145
151
|
build_timeout=self.build_timeout,
|
152
|
+
max_concurrent_inputs=self.max_concurrent_inputs,
|
153
|
+
target_concurrent_inputs=self.target_concurrent_inputs,
|
146
154
|
)
|
147
155
|
|
148
156
|
|
@@ -722,3 +730,49 @@ def _batched(
|
|
722
730
|
)
|
723
731
|
|
724
732
|
return wrapper
|
733
|
+
|
734
|
+
|
735
|
+
def _concurrent(
|
736
|
+
_warn_parentheses_missing=None,
|
737
|
+
*,
|
738
|
+
max_inputs: int, # Hard limit on each container's input concurrency
|
739
|
+
target_inputs: Optional[int] = None, # Input concurrency that Modal's autoscaler should target
|
740
|
+
) -> Callable[[Union[Callable[..., Any], _PartialFunction]], _PartialFunction]:
|
741
|
+
"""Decorator that allows individual containers to handle multiple inputs concurrently.
|
742
|
+
|
743
|
+
The concurrency mechanism depends on whether the function is async or not:
|
744
|
+
- Async functions will run inputs on a single thread as asyncio tasks.
|
745
|
+
- Synchronous functions will use multi-threading. The code must be thread-safe.
|
746
|
+
|
747
|
+
Input concurrency will be most useful for workflows that are IO-bound
|
748
|
+
(e.g., making network requests) or when running an inference server that supports
|
749
|
+
dynamic batching.
|
750
|
+
|
751
|
+
When `target_inputs` is set, Modal's autoscaler will try to provision resources such
|
752
|
+
that each container is running that many inputs concurrently. Containers may burst up to
|
753
|
+
up to `max_inputs` if resources are insufficient to remain at the target concurrency.
|
754
|
+
"""
|
755
|
+
if _warn_parentheses_missing is not None:
|
756
|
+
raise InvalidError(
|
757
|
+
"Positional arguments are not allowed. Did you forget parentheses? Suggestion: `@modal.concurrent()`."
|
758
|
+
)
|
759
|
+
|
760
|
+
if target_inputs and target_inputs > max_inputs:
|
761
|
+
raise InvalidError("`target_inputs` parameter cannot be greater than `max_inputs`.")
|
762
|
+
|
763
|
+
def wrapper(obj: Union[Callable[..., Any], _PartialFunction]) -> _PartialFunction:
|
764
|
+
if isinstance(obj, _PartialFunction):
|
765
|
+
# Risky that we need to mutate the parameters here; should make this safer
|
766
|
+
obj.max_concurrent_inputs = max_inputs
|
767
|
+
obj.target_concurrent_inputs = target_inputs
|
768
|
+
obj.add_flags(_PartialFunctionFlags.FUNCTION)
|
769
|
+
return obj
|
770
|
+
|
771
|
+
return _PartialFunction(
|
772
|
+
obj,
|
773
|
+
_PartialFunctionFlags.FUNCTION,
|
774
|
+
max_concurrent_inputs=max_inputs,
|
775
|
+
target_concurrent_inputs=target_inputs,
|
776
|
+
)
|
777
|
+
|
778
|
+
return wrapper
|
@@ -678,6 +678,12 @@ class _App:
|
|
678
678
|
is_generator = f.is_generator
|
679
679
|
batch_max_size = f.batch_max_size
|
680
680
|
batch_wait_ms = f.batch_wait_ms
|
681
|
+
if f.max_concurrent_inputs: # Using @modal.concurrent()
|
682
|
+
max_concurrent_inputs = f.max_concurrent_inputs
|
683
|
+
target_concurrent_inputs = f.target_concurrent_inputs
|
684
|
+
else:
|
685
|
+
max_concurrent_inputs = allow_concurrent_inputs
|
686
|
+
target_concurrent_inputs = None
|
681
687
|
else:
|
682
688
|
if not is_global_object(f.__qualname__) and not serialized:
|
683
689
|
raise InvalidError(
|
@@ -709,10 +715,12 @@ class _App:
|
|
709
715
|
)
|
710
716
|
|
711
717
|
info = FunctionInfo(f, serialized=serialized, name_override=name)
|
718
|
+
raw_f = f
|
712
719
|
webhook_config = None
|
713
720
|
batch_max_size = None
|
714
721
|
batch_wait_ms = None
|
715
|
-
|
722
|
+
max_concurrent_inputs = allow_concurrent_inputs
|
723
|
+
target_concurrent_inputs = None
|
716
724
|
|
717
725
|
cluster_size = None # Experimental: Clustered functions
|
718
726
|
i6pn_enabled = i6pn
|
@@ -753,7 +761,8 @@ class _App:
|
|
753
761
|
max_containers=max_containers,
|
754
762
|
buffer_containers=buffer_containers,
|
755
763
|
scaledown_window=scaledown_window,
|
756
|
-
|
764
|
+
max_concurrent_inputs=max_concurrent_inputs,
|
765
|
+
target_concurrent_inputs=target_concurrent_inputs,
|
757
766
|
batch_max_size=batch_max_size,
|
758
767
|
batch_wait_ms=batch_wait_ms,
|
759
768
|
timeout=timeout,
|
@@ -832,7 +841,7 @@ class _App:
|
|
832
841
|
concurrency_limit: Optional[int] = None, # Replaced with `max_containers`
|
833
842
|
container_idle_timeout: Optional[int] = None, # Replaced with `scaledown_window`
|
834
843
|
_experimental_buffer_containers: Optional[int] = None, # Now stable API with `buffer_containers`
|
835
|
-
) -> Callable[[CLS_T], CLS_T]:
|
844
|
+
) -> Callable[[Union[CLS_T, _PartialFunction]], CLS_T]:
|
836
845
|
"""
|
837
846
|
Decorator to register a new Modal [Cls](/docs/reference/modal.Cls) with this App.
|
838
847
|
"""
|
@@ -845,8 +854,21 @@ class _App:
|
|
845
854
|
raise InvalidError("`region` and `_experimental_scheduler_placement` cannot be used together")
|
846
855
|
scheduler_placement = SchedulerPlacement(region=region)
|
847
856
|
|
848
|
-
def wrapper(
|
857
|
+
def wrapper(wrapped_cls: Union[CLS_T, _PartialFunction]) -> CLS_T:
|
849
858
|
# Check if the decorated object is a class
|
859
|
+
if isinstance(wrapped_cls, _PartialFunction):
|
860
|
+
wrapped_cls.wrapped = True
|
861
|
+
user_cls = wrapped_cls.raw_f
|
862
|
+
if wrapped_cls.max_concurrent_inputs: # Using @modal.concurrent()
|
863
|
+
max_concurrent_inputs = wrapped_cls.max_concurrent_inputs
|
864
|
+
target_concurrent_inputs = wrapped_cls.target_concurrent_inputs
|
865
|
+
else:
|
866
|
+
max_concurrent_inputs = allow_concurrent_inputs
|
867
|
+
target_concurrent_inputs = None
|
868
|
+
else:
|
869
|
+
user_cls = wrapped_cls
|
870
|
+
max_concurrent_inputs = allow_concurrent_inputs
|
871
|
+
target_concurrent_inputs = None
|
850
872
|
if not inspect.isclass(user_cls):
|
851
873
|
raise TypeError("The @app.cls decorator must be used on a class.")
|
852
874
|
|
@@ -871,6 +893,12 @@ class _App:
|
|
871
893
|
):
|
872
894
|
raise InvalidError("A class must have `enable_memory_snapshot=True` to use `snap=True` on its methods.")
|
873
895
|
|
896
|
+
for method in _find_partial_methods_for_user_cls(user_cls, _PartialFunctionFlags.FUNCTION).values():
|
897
|
+
if method.max_concurrent_inputs:
|
898
|
+
raise InvalidError(
|
899
|
+
"The `@modal.concurrent` decorator cannot be used on methods; decorate the class instead."
|
900
|
+
)
|
901
|
+
|
874
902
|
info = FunctionInfo(None, serialized=serialized, user_cls=user_cls)
|
875
903
|
|
876
904
|
cls_func = _Function.from_local(
|
@@ -892,7 +920,8 @@ class _App:
|
|
892
920
|
scaledown_window=scaledown_window,
|
893
921
|
proxy=proxy,
|
894
922
|
retries=retries,
|
895
|
-
|
923
|
+
max_concurrent_inputs=max_concurrent_inputs,
|
924
|
+
target_concurrent_inputs=target_concurrent_inputs,
|
896
925
|
batch_max_size=batch_max_size,
|
897
926
|
batch_wait_ms=batch_wait_ms,
|
898
927
|
timeout=timeout,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import collections.abc
|
2
2
|
import modal._functions
|
3
3
|
import modal._object
|
4
|
+
import modal._partial_function
|
4
5
|
import modal._utils.function_utils
|
5
6
|
import modal.client
|
6
7
|
import modal.cloud_bucket_mount
|
@@ -247,7 +248,7 @@ class _App:
|
|
247
248
|
concurrency_limit: typing.Optional[int] = None,
|
248
249
|
container_idle_timeout: typing.Optional[int] = None,
|
249
250
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
250
|
-
) -> collections.abc.Callable[[CLS_T], CLS_T]: ...
|
251
|
+
) -> collections.abc.Callable[[typing.Union[CLS_T, modal._partial_function._PartialFunction]], CLS_T]: ...
|
251
252
|
async def spawn_sandbox(
|
252
253
|
self,
|
253
254
|
*entrypoint_args: str,
|
@@ -487,7 +488,7 @@ class App:
|
|
487
488
|
concurrency_limit: typing.Optional[int] = None,
|
488
489
|
container_idle_timeout: typing.Optional[int] = None,
|
489
490
|
_experimental_buffer_containers: typing.Optional[int] = None,
|
490
|
-
) -> collections.abc.Callable[[CLS_T], CLS_T]: ...
|
491
|
+
) -> collections.abc.Callable[[typing.Union[CLS_T, modal.partial_function.PartialFunction]], CLS_T]: ...
|
491
492
|
|
492
493
|
class __spawn_sandbox_spec(typing_extensions.Protocol[SUPERSELF]):
|
493
494
|
def __call__(
|
@@ -31,7 +31,7 @@ class _Client:
|
|
31
31
|
server_url: str,
|
32
32
|
client_type: int,
|
33
33
|
credentials: typing.Optional[tuple[str, str]],
|
34
|
-
version: str = "0.73.
|
34
|
+
version: str = "0.73.128",
|
35
35
|
): ...
|
36
36
|
def is_closed(self) -> bool: ...
|
37
37
|
@property
|
@@ -93,7 +93,7 @@ class Client:
|
|
93
93
|
server_url: str,
|
94
94
|
client_type: int,
|
95
95
|
credentials: typing.Optional[tuple[str, str]],
|
96
|
-
version: str = "0.73.
|
96
|
+
version: str = "0.73.128",
|
97
97
|
): ...
|
98
98
|
def is_closed(self) -> bool: ...
|
99
99
|
@property
|
@@ -82,7 +82,8 @@ class Function(
|
|
82
82
|
max_containers: typing.Optional[int] = None,
|
83
83
|
buffer_containers: typing.Optional[int] = None,
|
84
84
|
scaledown_window: typing.Optional[int] = None,
|
85
|
-
|
85
|
+
max_concurrent_inputs: typing.Optional[int] = None,
|
86
|
+
target_concurrent_inputs: typing.Optional[int] = None,
|
86
87
|
batch_max_size: typing.Optional[int] = None,
|
87
88
|
batch_wait_ms: typing.Optional[int] = None,
|
88
89
|
cloud: typing.Optional[str] = None,
|
@@ -5,6 +5,7 @@ from ._partial_function import (
|
|
5
5
|
_asgi_app,
|
6
6
|
_batched,
|
7
7
|
_build,
|
8
|
+
_concurrent,
|
8
9
|
_enter,
|
9
10
|
_exit,
|
10
11
|
_fastapi_endpoint,
|
@@ -28,3 +29,4 @@ build = synchronize_api(_build, target_module=__name__)
|
|
28
29
|
enter = synchronize_api(_enter, target_module=__name__)
|
29
30
|
exit = synchronize_api(_exit, target_module=__name__)
|
30
31
|
batched = synchronize_api(_batched, target_module=__name__)
|
32
|
+
concurrent = synchronize_api(_concurrent, target_module=__name__)
|
@@ -18,6 +18,8 @@ class PartialFunction(
|
|
18
18
|
force_build: bool
|
19
19
|
cluster_size: typing.Optional[int]
|
20
20
|
build_timeout: typing.Optional[int]
|
21
|
+
max_concurrent_inputs: typing.Optional[int]
|
22
|
+
target_concurrent_inputs: typing.Optional[int]
|
21
23
|
|
22
24
|
def __init__(
|
23
25
|
self,
|
@@ -31,6 +33,8 @@ class PartialFunction(
|
|
31
33
|
cluster_size: typing.Optional[int] = None,
|
32
34
|
force_build: bool = False,
|
33
35
|
build_timeout: typing.Optional[int] = None,
|
36
|
+
max_concurrent_inputs: typing.Optional[int] = None,
|
37
|
+
target_concurrent_inputs: typing.Optional[int] = None,
|
34
38
|
): ...
|
35
39
|
def _get_raw_f(self) -> collections.abc.Callable[modal._partial_function.P, modal._partial_function.ReturnType]: ...
|
36
40
|
def _is_web_endpoint(self) -> bool: ...
|
@@ -118,3 +122,8 @@ def exit(
|
|
118
122
|
def batched(
|
119
123
|
_warn_parentheses_missing=None, *, max_batch_size: int, wait_ms: int
|
120
124
|
) -> collections.abc.Callable[[collections.abc.Callable[..., typing.Any]], PartialFunction]: ...
|
125
|
+
def concurrent(
|
126
|
+
_warn_parentheses_missing=None, *, max_inputs: int, target_inputs: typing.Optional[int] = None
|
127
|
+
) -> collections.abc.Callable[
|
128
|
+
[typing.Union[collections.abc.Callable[..., typing.Any], PartialFunction]], PartialFunction
|
129
|
+
]: ...
|
@@ -44,7 +44,11 @@ _default_image: _Image = _Image.debian_slim()
|
|
44
44
|
# The maximum number of bytes that can be passed to an exec on Linux.
|
45
45
|
# Though this is technically a 'server side' limit, it is unlikely to change.
|
46
46
|
# getconf ARG_MAX will show this value on a host.
|
47
|
-
|
47
|
+
#
|
48
|
+
# By probing in production, the limit is 131072 bytes (2**17).
|
49
|
+
# We need some bytes of overhead for the rest of the command line besides the args,
|
50
|
+
# e.g. 'runsc exec ...'. So we use 2**16 as the limit.
|
51
|
+
ARG_MAX_BYTES = 2**16
|
48
52
|
|
49
53
|
if TYPE_CHECKING:
|
50
54
|
import modal.app
|
@@ -1216,7 +1216,7 @@ message Function {
|
|
1216
1216
|
|
1217
1217
|
repeated VolumeMount volume_mounts = 33;
|
1218
1218
|
|
1219
|
-
uint32
|
1219
|
+
uint32 max_concurrent_inputs = 34;
|
1220
1220
|
|
1221
1221
|
repeated CustomDomainInfo custom_domain_info = 35;
|
1222
1222
|
|
@@ -1266,7 +1266,7 @@ message Function {
|
|
1266
1266
|
uint64 batch_linger_ms = 61; // Miliseconds to block before a response is needed
|
1267
1267
|
bool i6pn_enabled = 62;
|
1268
1268
|
bool _experimental_concurrent_cancellations = 63;
|
1269
|
-
uint32
|
1269
|
+
uint32 target_concurrent_inputs = 64;
|
1270
1270
|
|
1271
1271
|
// TODO(irfansharif): Remove, once https://github.com/modal-labs/modal/pull/15645 lands.
|
1272
1272
|
bool _experimental_task_templates_enabled = 65; // forces going through the new gpu-fallbacks integration path, even if no fallback options are specified
|