modAL 1.1.4__tar.gz → 1.1.4.dev1__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.1.4 → modal-1.1.4.dev1}/PKG-INFO +1 -1
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_clustered_functions.py +0 -3
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_clustered_functions.pyi +2 -3
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_functions.py +0 -11
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/asgi.py +1 -1
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/grpc_utils.py +0 -1
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/app.py +2 -6
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/app.pyi +0 -4
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2025.06.txt +0 -1
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/PREVIEW.txt +0 -1
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/client.pyi +10 -2
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cls.py +1 -6
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cls.pyi +0 -16
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/experimental/__init__.py +1 -2
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/experimental/flash.py +23 -183
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/experimental/flash.pyi +9 -83
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/functions.pyi +0 -12
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/image.py +2 -8
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/image.pyi +4 -16
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/mount.py +11 -17
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/mount.pyi +0 -4
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/parallel_map.py +4 -15
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/parallel_map.pyi +0 -1
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/sandbox.py +4 -31
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/sandbox.pyi +3 -12
- {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api.proto +0 -29
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_grpc.py +0 -32
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_pb2.py +853 -893
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_pb2.pyi +4 -90
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_pb2_grpc.py +1 -68
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_pb2_grpc.pyi +3 -25
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/modal_api_grpc.py +0 -2
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_version/__init__.py +1 -1
- {modal-1.1.4 → modal-1.1.4.dev1}/LICENSE +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/README.md +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/__init__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/__main__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_container_entrypoint.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_ipython.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_location.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_object.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_output.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_partial_function.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_pty.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_resolver.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_resources.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/__init__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/execution_context.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/telemetry.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_serialization.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_traceback.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_tunnel.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_tunnel.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_type_manager.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/__init__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/app_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/async_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/blob_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/deprecation.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/docker_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/function_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/git_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/hash_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/http_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/logger.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/mount_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/name_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/package_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/shell_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/time_utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_vendor/__init__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_vendor/tblib.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/_watcher.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2023.12.txt +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2024.04.txt +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2024.10.txt +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/README.md +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/base-images.json +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/call_graph.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/__init__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/_download.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/_traceback.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/app.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/cluster.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/config.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/container.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/dict.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/entry_point.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/environment.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/import_refs.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/launch.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/network_file_system.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/profile.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/__init__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/vscode.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/queues.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/run.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/secret.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/token.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/utils.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/volume.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/client.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/config.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/container_process.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/container_process.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/dict.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/dict.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/environments.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/environments.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/exception.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/experimental/ipython.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/file_io.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/file_io.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/file_pattern_matcher.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/functions.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/gpu.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/io_streams.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/io_streams.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/network_file_system.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/network_file_system.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/object.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/object.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/output.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/partial_function.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/partial_function.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/proxy.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/proxy.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/py.typed +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/queue.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/queue.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/retries.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/runner.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/runner.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/running_app.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/schedule.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/scheduler_placement.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/secret.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/secret.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/serving.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/serving.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/snapshot.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/snapshot.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/stream_type.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/token_flow.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/token_flow.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/volume.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal/volume.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/requires.txt +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/top_level.txt +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/__init__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/__init__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/modal_options_grpc.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options.proto +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_grpc.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_pb2.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_pb2.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/py.typed +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/modal_version/__main__.py +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/pyproject.toml +0 -0
- {modal-1.1.4 → modal-1.1.4.dev1}/setup.cfg +0 -0
|
@@ -14,7 +14,6 @@ from modal_proto import api_pb2
|
|
|
14
14
|
@dataclass
|
|
15
15
|
class ClusterInfo:
|
|
16
16
|
rank: int
|
|
17
|
-
cluster_id: str
|
|
18
17
|
container_ips: list[str]
|
|
19
18
|
container_ipv4_ips: list[str]
|
|
20
19
|
|
|
@@ -70,14 +69,12 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
70
69
|
)
|
|
71
70
|
cluster_info = ClusterInfo(
|
|
72
71
|
rank=resp.cluster_rank,
|
|
73
|
-
cluster_id=resp.cluster_id,
|
|
74
72
|
container_ips=resp.container_ips,
|
|
75
73
|
container_ipv4_ips=resp.container_ipv4_ips,
|
|
76
74
|
)
|
|
77
75
|
else:
|
|
78
76
|
cluster_info = ClusterInfo(
|
|
79
77
|
rank=0,
|
|
80
|
-
cluster_id="", # No cluster ID for single-node # TODO(irfansharif): Is this right?
|
|
81
78
|
container_ips=[container_ip],
|
|
82
79
|
container_ipv4_ips=[], # No IPv4 IPs for single-node
|
|
83
80
|
)
|
|
@@ -3,14 +3,13 @@ import typing
|
|
|
3
3
|
import typing_extensions
|
|
4
4
|
|
|
5
5
|
class ClusterInfo:
|
|
6
|
-
"""ClusterInfo(rank: int,
|
|
6
|
+
"""ClusterInfo(rank: int, container_ips: list[str], container_ipv4_ips: list[str])"""
|
|
7
7
|
|
|
8
8
|
rank: int
|
|
9
|
-
cluster_id: str
|
|
10
9
|
container_ips: list[str]
|
|
11
10
|
container_ipv4_ips: list[str]
|
|
12
11
|
|
|
13
|
-
def __init__(self, rank: int,
|
|
12
|
+
def __init__(self, rank: int, container_ips: list[str], container_ipv4_ips: list[str]) -> None:
|
|
14
13
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
|
15
14
|
...
|
|
16
15
|
|
|
@@ -674,7 +674,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
674
674
|
proxy: Optional[_Proxy] = None,
|
|
675
675
|
retries: Optional[Union[int, Retries]] = None,
|
|
676
676
|
timeout: int = 300,
|
|
677
|
-
startup_timeout: Optional[int] = None,
|
|
678
677
|
min_containers: Optional[int] = None,
|
|
679
678
|
max_containers: Optional[int] = None,
|
|
680
679
|
buffer_containers: Optional[int] = None,
|
|
@@ -967,7 +966,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
967
966
|
proxy_id=(proxy.object_id if proxy else None),
|
|
968
967
|
retry_policy=retry_policy,
|
|
969
968
|
timeout_secs=timeout_secs or 0,
|
|
970
|
-
startup_timeout_secs=startup_timeout or timeout_secs,
|
|
971
969
|
pty_info=pty_info,
|
|
972
970
|
cloud_provider_str=cloud if cloud else "",
|
|
973
971
|
runtime=config.get("function_runtime"),
|
|
@@ -1021,7 +1019,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1021
1019
|
autoscaler_settings=function_definition.autoscaler_settings,
|
|
1022
1020
|
worker_id=function_definition.worker_id,
|
|
1023
1021
|
timeout_secs=function_definition.timeout_secs,
|
|
1024
|
-
startup_timeout_secs=function_definition.startup_timeout_secs,
|
|
1025
1022
|
web_url=function_definition.web_url,
|
|
1026
1023
|
web_url_info=function_definition.web_url_info,
|
|
1027
1024
|
webhook_config=function_definition.webhook_config,
|
|
@@ -1474,7 +1471,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1474
1471
|
self._info = None
|
|
1475
1472
|
self._serve_mounts = frozenset()
|
|
1476
1473
|
self._metadata = None
|
|
1477
|
-
self._experimental_flash_urls = None
|
|
1478
1474
|
|
|
1479
1475
|
def _hydrate_metadata(self, metadata: Optional[Message]):
|
|
1480
1476
|
# Overridden concrete implementation of base class method
|
|
@@ -1502,7 +1498,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1502
1498
|
self._max_object_size_bytes = (
|
|
1503
1499
|
metadata.max_object_size_bytes if metadata.HasField("max_object_size_bytes") else MAX_OBJECT_SIZE_BYTES
|
|
1504
1500
|
)
|
|
1505
|
-
self._experimental_flash_urls = metadata._experimental_flash_urls
|
|
1506
1501
|
|
|
1507
1502
|
def _get_metadata(self):
|
|
1508
1503
|
# Overridden concrete implementation of base class method
|
|
@@ -1520,7 +1515,6 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1520
1515
|
input_plane_url=self._input_plane_url,
|
|
1521
1516
|
input_plane_region=self._input_plane_region,
|
|
1522
1517
|
max_object_size_bytes=self._max_object_size_bytes,
|
|
1523
|
-
_experimental_flash_urls=self._experimental_flash_urls,
|
|
1524
1518
|
)
|
|
1525
1519
|
|
|
1526
1520
|
def _check_no_web_url(self, fn_name: str):
|
|
@@ -1551,11 +1545,6 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1551
1545
|
"""URL of a Function running as a web endpoint."""
|
|
1552
1546
|
return self._web_url
|
|
1553
1547
|
|
|
1554
|
-
@live_method
|
|
1555
|
-
async def _experimental_get_flash_urls(self) -> Optional[list[str]]:
|
|
1556
|
-
"""URL of the flash service for the function."""
|
|
1557
|
-
return list(self._experimental_flash_urls) if self._experimental_flash_urls else None
|
|
1558
|
-
|
|
1559
1548
|
@property
|
|
1560
1549
|
async def is_generator(self) -> bool:
|
|
1561
1550
|
"""mdmd:hidden"""
|
|
@@ -120,7 +120,7 @@ def asgi_app_wrapper(asgi_app, container_io_manager) -> tuple[Callable[..., Asyn
|
|
|
120
120
|
|
|
121
121
|
async def handle_first_input_timeout():
|
|
122
122
|
if scope["type"] == "http":
|
|
123
|
-
await messages_from_app.put({"type": "http.response.start", "status":
|
|
123
|
+
await messages_from_app.put({"type": "http.response.start", "status": 502})
|
|
124
124
|
await messages_from_app.put(
|
|
125
125
|
{
|
|
126
126
|
"type": "http.response.body",
|
|
@@ -641,8 +641,7 @@ class _App:
|
|
|
641
641
|
scaledown_window: Optional[int] = None, # Max time (in seconds) a container can remain idle while scaling down.
|
|
642
642
|
proxy: Optional[_Proxy] = None, # Reference to a Modal Proxy to use in front of this function.
|
|
643
643
|
retries: Optional[Union[int, Retries]] = None, # Number of times to retry each input in case of failure.
|
|
644
|
-
timeout: int = 300, # Maximum execution time
|
|
645
|
-
startup_timeout: Optional[int] = None, # Maximum startup time in seconds with higher precedence than `timeout`.
|
|
644
|
+
timeout: int = 300, # Maximum execution time in seconds.
|
|
646
645
|
name: Optional[str] = None, # Sets the Modal name of the function within the app
|
|
647
646
|
is_generator: Optional[
|
|
648
647
|
bool
|
|
@@ -817,7 +816,6 @@ class _App:
|
|
|
817
816
|
batch_max_size=batch_max_size,
|
|
818
817
|
batch_wait_ms=batch_wait_ms,
|
|
819
818
|
timeout=timeout,
|
|
820
|
-
startup_timeout=startup_timeout or timeout,
|
|
821
819
|
cloud=cloud,
|
|
822
820
|
webhook_config=webhook_config,
|
|
823
821
|
enable_memory_snapshot=enable_memory_snapshot,
|
|
@@ -871,8 +869,7 @@ class _App:
|
|
|
871
869
|
scaledown_window: Optional[int] = None, # Max time (in seconds) a container can remain idle while scaling down.
|
|
872
870
|
proxy: Optional[_Proxy] = None, # Reference to a Modal Proxy to use in front of this function.
|
|
873
871
|
retries: Optional[Union[int, Retries]] = None, # Number of times to retry each input in case of failure.
|
|
874
|
-
timeout: int = 300, # Maximum execution time
|
|
875
|
-
startup_timeout: Optional[int] = None, # Maximum startup time in seconds with higher precedence than `timeout`.
|
|
872
|
+
timeout: int = 300, # Maximum execution time in seconds; applies independently to startup and each input.
|
|
876
873
|
cloud: Optional[str] = None, # Cloud provider to run the function on. Possible values are aws, gcp, oci, auto.
|
|
877
874
|
region: Optional[Union[str, Sequence[str]]] = None, # Region or regions to run the function on.
|
|
878
875
|
enable_memory_snapshot: bool = False, # Enable memory checkpointing for faster cold starts.
|
|
@@ -1005,7 +1002,6 @@ class _App:
|
|
|
1005
1002
|
batch_max_size=batch_max_size,
|
|
1006
1003
|
batch_wait_ms=batch_wait_ms,
|
|
1007
1004
|
timeout=timeout,
|
|
1008
|
-
startup_timeout=startup_timeout or timeout,
|
|
1009
1005
|
cloud=cloud,
|
|
1010
1006
|
enable_memory_snapshot=enable_memory_snapshot,
|
|
1011
1007
|
block_network=block_network,
|
|
@@ -411,7 +411,6 @@ class _App:
|
|
|
411
411
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
|
412
412
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
|
413
413
|
timeout: int = 300,
|
|
414
|
-
startup_timeout: typing.Optional[int] = None,
|
|
415
414
|
name: typing.Optional[str] = None,
|
|
416
415
|
is_generator: typing.Optional[bool] = None,
|
|
417
416
|
cloud: typing.Optional[str] = None,
|
|
@@ -465,7 +464,6 @@ class _App:
|
|
|
465
464
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
|
466
465
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
|
467
466
|
timeout: int = 300,
|
|
468
|
-
startup_timeout: typing.Optional[int] = None,
|
|
469
467
|
cloud: typing.Optional[str] = None,
|
|
470
468
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
471
469
|
enable_memory_snapshot: bool = False,
|
|
@@ -1016,7 +1014,6 @@ class App:
|
|
|
1016
1014
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
1017
1015
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
|
1018
1016
|
timeout: int = 300,
|
|
1019
|
-
startup_timeout: typing.Optional[int] = None,
|
|
1020
1017
|
name: typing.Optional[str] = None,
|
|
1021
1018
|
is_generator: typing.Optional[bool] = None,
|
|
1022
1019
|
cloud: typing.Optional[str] = None,
|
|
@@ -1070,7 +1067,6 @@ class App:
|
|
|
1070
1067
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
|
1071
1068
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
|
1072
1069
|
timeout: int = 300,
|
|
1073
|
-
startup_timeout: typing.Optional[int] = None,
|
|
1074
1070
|
cloud: typing.Optional[str] = None,
|
|
1075
1071
|
region: typing.Union[str, collections.abc.Sequence[str], None] = None,
|
|
1076
1072
|
enable_memory_snapshot: bool = False,
|
|
@@ -29,7 +29,11 @@ class _Client:
|
|
|
29
29
|
_snapshotted: bool
|
|
30
30
|
|
|
31
31
|
def __init__(
|
|
32
|
-
self,
|
|
32
|
+
self,
|
|
33
|
+
server_url: str,
|
|
34
|
+
client_type: int,
|
|
35
|
+
credentials: typing.Optional[tuple[str, str]],
|
|
36
|
+
version: str = "1.1.4.dev1",
|
|
33
37
|
):
|
|
34
38
|
"""mdmd:hidden
|
|
35
39
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -156,7 +160,11 @@ class Client:
|
|
|
156
160
|
_snapshotted: bool
|
|
157
161
|
|
|
158
162
|
def __init__(
|
|
159
|
-
self,
|
|
163
|
+
self,
|
|
164
|
+
server_url: str,
|
|
165
|
+
client_type: int,
|
|
166
|
+
credentials: typing.Optional[tuple[str, str]],
|
|
167
|
+
version: str = "1.1.4.dev1",
|
|
160
168
|
):
|
|
161
169
|
"""mdmd:hidden
|
|
162
170
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -12,7 +12,7 @@ from grpclib import GRPCError, Status
|
|
|
12
12
|
from modal_proto import api_pb2
|
|
13
13
|
|
|
14
14
|
from ._functions import _Function, _parse_retries
|
|
15
|
-
from ._object import _Object
|
|
15
|
+
from ._object import _Object
|
|
16
16
|
from ._partial_function import (
|
|
17
17
|
_find_callables_for_obj,
|
|
18
18
|
_find_partial_methods_for_user_cls,
|
|
@@ -510,11 +510,6 @@ class _Cls(_Object, type_prefix="cs"):
|
|
|
510
510
|
# returns method names for a *local* class only for now (used by cli)
|
|
511
511
|
return self._method_partials.keys()
|
|
512
512
|
|
|
513
|
-
@live_method
|
|
514
|
-
async def _experimental_get_flash_urls(self) -> Optional[list[str]]:
|
|
515
|
-
"""URL of the flash service for the class."""
|
|
516
|
-
return await self._get_class_service_function()._experimental_get_flash_urls()
|
|
517
|
-
|
|
518
513
|
def _hydrate_metadata(self, metadata: Message):
|
|
519
514
|
assert isinstance(metadata, api_pb2.ClassHandleMetadata)
|
|
520
515
|
class_service_function = self._get_class_service_function()
|
|
@@ -354,10 +354,6 @@ class _Cls(modal._object._Object):
|
|
|
354
354
|
def _get_name(self) -> str: ...
|
|
355
355
|
def _get_class_service_function(self) -> modal._functions._Function: ...
|
|
356
356
|
def _get_method_names(self) -> collections.abc.Collection[str]: ...
|
|
357
|
-
async def _experimental_get_flash_urls(self) -> typing.Optional[list[str]]:
|
|
358
|
-
"""URL of the flash service for the class."""
|
|
359
|
-
...
|
|
360
|
-
|
|
361
357
|
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
|
362
358
|
@staticmethod
|
|
363
359
|
def validate_construction_mechanism(user_cls):
|
|
@@ -524,18 +520,6 @@ class Cls(modal.object.Object):
|
|
|
524
520
|
def _get_name(self) -> str: ...
|
|
525
521
|
def _get_class_service_function(self) -> modal.functions.Function: ...
|
|
526
522
|
def _get_method_names(self) -> collections.abc.Collection[str]: ...
|
|
527
|
-
|
|
528
|
-
class ___experimental_get_flash_urls_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
529
|
-
def __call__(self, /) -> typing.Optional[list[str]]:
|
|
530
|
-
"""URL of the flash service for the class."""
|
|
531
|
-
...
|
|
532
|
-
|
|
533
|
-
async def aio(self, /) -> typing.Optional[list[str]]:
|
|
534
|
-
"""URL of the flash service for the class."""
|
|
535
|
-
...
|
|
536
|
-
|
|
537
|
-
_experimental_get_flash_urls: ___experimental_get_flash_urls_spec[typing_extensions.Self]
|
|
538
|
-
|
|
539
523
|
def _hydrate_metadata(self, metadata: google.protobuf.message.Message): ...
|
|
540
524
|
@staticmethod
|
|
541
525
|
def validate_construction_mechanism(user_cls):
|
|
@@ -311,8 +311,7 @@ async def notebook_base_image(*, python_version: Optional[str] = None, force_bui
|
|
|
311
311
|
|
|
312
312
|
commands: list[str] = [
|
|
313
313
|
"apt-get update",
|
|
314
|
-
"apt-get install -y "
|
|
315
|
-
+ "libpq-dev pkg-config cmake git curl wget unzip zip libsqlite3-dev openssh-server vim ffmpeg",
|
|
314
|
+
"apt-get install -y libpq-dev pkg-config cmake git curl wget unzip zip libsqlite3-dev openssh-server vim",
|
|
316
315
|
_install_cuda_command(),
|
|
317
316
|
# Install uv since it's faster than pip for installing packages.
|
|
318
317
|
"pip install uv",
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
2
|
import asyncio
|
|
3
3
|
import math
|
|
4
|
-
import os
|
|
5
|
-
import subprocess
|
|
6
4
|
import sys
|
|
7
5
|
import time
|
|
8
6
|
import traceback
|
|
@@ -21,87 +19,28 @@ from ..client import _Client
|
|
|
21
19
|
from ..config import logger
|
|
22
20
|
from ..exception import InvalidError
|
|
23
21
|
|
|
24
|
-
MAX_FAILURES = 3
|
|
25
|
-
|
|
26
22
|
|
|
27
23
|
class _FlashManager:
|
|
28
|
-
def __init__(
|
|
29
|
-
self,
|
|
30
|
-
client: _Client,
|
|
31
|
-
port: int,
|
|
32
|
-
process: Optional[subprocess.Popen] = None,
|
|
33
|
-
health_check_url: Optional[str] = None,
|
|
34
|
-
):
|
|
24
|
+
def __init__(self, client: _Client, port: int, health_check_url: Optional[str] = None):
|
|
35
25
|
self.client = client
|
|
36
26
|
self.port = port
|
|
37
|
-
# Health check is not currently being used
|
|
38
27
|
self.health_check_url = health_check_url
|
|
39
|
-
self.process = process
|
|
40
28
|
self.tunnel_manager = _forward_tunnel(port, client=client)
|
|
41
29
|
self.stopped = False
|
|
42
|
-
self.num_failures = 0
|
|
43
|
-
self.task_id = os.environ["MODAL_TASK_ID"]
|
|
44
|
-
|
|
45
|
-
async def check_port_connection(self, process: Optional[subprocess.Popen], timeout: int = 10):
|
|
46
|
-
import socket
|
|
47
|
-
|
|
48
|
-
start_time = time.monotonic()
|
|
49
|
-
|
|
50
|
-
while time.monotonic() - start_time < timeout:
|
|
51
|
-
try:
|
|
52
|
-
if process is not None and process.poll() is not None:
|
|
53
|
-
return Exception(f"Process {process.pid} exited with code {process.returncode}")
|
|
54
|
-
with socket.create_connection(("localhost", self.port), timeout=1):
|
|
55
|
-
return
|
|
56
|
-
except (ConnectionRefusedError, OSError):
|
|
57
|
-
await asyncio.sleep(0.1)
|
|
58
|
-
|
|
59
|
-
return Exception(f"Waited too long for port {self.port} to start accepting connections")
|
|
60
30
|
|
|
61
31
|
async def _start(self):
|
|
62
32
|
self.tunnel = await self.tunnel_manager.__aenter__()
|
|
33
|
+
|
|
63
34
|
parsed_url = urlparse(self.tunnel.url)
|
|
64
35
|
host = parsed_url.hostname
|
|
65
36
|
port = parsed_url.port or 443
|
|
66
37
|
|
|
67
38
|
self.heartbeat_task = asyncio.create_task(self._run_heartbeat(host, port))
|
|
68
|
-
self.drain_task = asyncio.create_task(self._drain_container())
|
|
69
|
-
|
|
70
|
-
async def _drain_container(self):
|
|
71
|
-
"""
|
|
72
|
-
Background task that checks if we've encountered too many failures and drains the container if so.
|
|
73
|
-
"""
|
|
74
|
-
while True:
|
|
75
|
-
try:
|
|
76
|
-
# Check if the container should be drained (e.g., too many failures)
|
|
77
|
-
if self.num_failures > MAX_FAILURES:
|
|
78
|
-
logger.warning(
|
|
79
|
-
f"[Modal Flash] Draining task {self.task_id} on {self.tunnel.url} due to too many failures."
|
|
80
|
-
)
|
|
81
|
-
await self.stop()
|
|
82
|
-
# handle close upon container exit
|
|
83
|
-
|
|
84
|
-
if self.task_id:
|
|
85
|
-
await self.client.stub.ContainerStop(api_pb2.ContainerStopRequest(task_id=self.task_id))
|
|
86
|
-
return
|
|
87
|
-
except asyncio.CancelledError:
|
|
88
|
-
logger.warning("[Modal Flash] Shutting down...")
|
|
89
|
-
return
|
|
90
|
-
except Exception as e:
|
|
91
|
-
logger.error(f"[Modal Flash] Error draining container: {e}")
|
|
92
|
-
await asyncio.sleep(1)
|
|
93
|
-
|
|
94
|
-
try:
|
|
95
|
-
await asyncio.sleep(1)
|
|
96
|
-
except asyncio.CancelledError:
|
|
97
|
-
logger.warning("[Modal Flash] Shutting down...")
|
|
98
|
-
return
|
|
99
39
|
|
|
100
40
|
async def _run_heartbeat(self, host: str, port: int):
|
|
101
41
|
first_registration = True
|
|
102
42
|
while True:
|
|
103
43
|
try:
|
|
104
|
-
await self.check_port_connection(process=self.process)
|
|
105
44
|
resp = await self.client.stub.FlashContainerRegister(
|
|
106
45
|
api_pb2.FlashContainerRegisterRequest(
|
|
107
46
|
priority=10,
|
|
@@ -111,25 +50,14 @@ class _FlashManager:
|
|
|
111
50
|
),
|
|
112
51
|
timeout=10,
|
|
113
52
|
)
|
|
114
|
-
self.num_failures = 0
|
|
115
53
|
if first_registration:
|
|
116
|
-
logger.warning(
|
|
117
|
-
f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}"
|
|
118
|
-
)
|
|
54
|
+
logger.warning(f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url}")
|
|
119
55
|
first_registration = False
|
|
120
56
|
except asyncio.CancelledError:
|
|
121
57
|
logger.warning("[Modal Flash] Shutting down...")
|
|
122
58
|
break
|
|
123
59
|
except Exception as e:
|
|
124
60
|
logger.error(f"[Modal Flash] Heartbeat failed: {e}")
|
|
125
|
-
self.num_failures += 1
|
|
126
|
-
logger.error(
|
|
127
|
-
f"[Modal Flash] Deregistering container {self.tunnel.url}, num_failures: {self.num_failures}"
|
|
128
|
-
)
|
|
129
|
-
await retry_transient_errors(
|
|
130
|
-
self.client.stub.FlashContainerDeregister,
|
|
131
|
-
api_pb2.FlashContainerDeregisterRequest(),
|
|
132
|
-
)
|
|
133
61
|
|
|
134
62
|
try:
|
|
135
63
|
await asyncio.sleep(1)
|
|
@@ -166,17 +94,16 @@ FlashManager = synchronize_api(_FlashManager)
|
|
|
166
94
|
|
|
167
95
|
|
|
168
96
|
@synchronizer.create_blocking
|
|
169
|
-
async def flash_forward(
|
|
170
|
-
port: int, process: Optional[subprocess.Popen] = None, health_check_url: Optional[str] = None
|
|
171
|
-
) -> _FlashManager:
|
|
97
|
+
async def flash_forward(port: int, health_check_url: Optional[str] = None) -> _FlashManager:
|
|
172
98
|
"""
|
|
173
99
|
Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
|
|
100
|
+
|
|
174
101
|
This is a highly experimental method that can break or be removed at any time without warning.
|
|
175
102
|
Do not use this method unless explicitly instructed to do so by Modal support.
|
|
176
103
|
"""
|
|
177
104
|
client = await _Client.from_env()
|
|
178
105
|
|
|
179
|
-
manager = _FlashManager(client, port,
|
|
106
|
+
manager = _FlashManager(client, port, health_check_url)
|
|
180
107
|
await manager._start()
|
|
181
108
|
return manager
|
|
182
109
|
|
|
@@ -200,8 +127,6 @@ class _FlashPrometheusAutoscaler:
|
|
|
200
127
|
scale_down_stabilization_window_seconds: int,
|
|
201
128
|
autoscaling_interval_seconds: int,
|
|
202
129
|
):
|
|
203
|
-
import aiohttp
|
|
204
|
-
|
|
205
130
|
if scale_up_stabilization_window_seconds > self._max_window_seconds:
|
|
206
131
|
raise InvalidError(
|
|
207
132
|
f"scale_up_stabilization_window_seconds must be less than or equal to {self._max_window_seconds}"
|
|
@@ -213,6 +138,8 @@ class _FlashPrometheusAutoscaler:
|
|
|
213
138
|
if target_metric_value <= 0:
|
|
214
139
|
raise InvalidError("target_metric_value must be greater than 0")
|
|
215
140
|
|
|
141
|
+
import aiohttp
|
|
142
|
+
|
|
216
143
|
self.client = client
|
|
217
144
|
self.app_name = app_name
|
|
218
145
|
self.cls_name = cls_name
|
|
@@ -273,10 +200,7 @@ class _FlashPrometheusAutoscaler:
|
|
|
273
200
|
if timestamp >= autoscaling_time - self._max_window_seconds
|
|
274
201
|
]
|
|
275
202
|
|
|
276
|
-
|
|
277
|
-
current_target_containers = await self._compute_target_containers_internal(current_replicas)
|
|
278
|
-
else:
|
|
279
|
-
current_target_containers = await self._compute_target_containers_prometheus(current_replicas)
|
|
203
|
+
current_target_containers = await self._compute_target_containers(current_replicas)
|
|
280
204
|
autoscaling_decisions.append((autoscaling_time, current_target_containers))
|
|
281
205
|
|
|
282
206
|
actual_target_containers = self._make_scaling_decision(
|
|
@@ -289,8 +213,8 @@ class _FlashPrometheusAutoscaler:
|
|
|
289
213
|
)
|
|
290
214
|
|
|
291
215
|
logger.warning(
|
|
292
|
-
f"[Modal Flash] Scaling to {actual_target_containers
|
|
293
|
-
f"
|
|
216
|
+
f"[Modal Flash] Scaling to {actual_target_containers} containers. Autoscaling decision "
|
|
217
|
+
f"made in {time.time() - autoscaling_time} seconds."
|
|
294
218
|
)
|
|
295
219
|
|
|
296
220
|
await self.autoscaling_decisions_dict.put(
|
|
@@ -299,7 +223,9 @@ class _FlashPrometheusAutoscaler:
|
|
|
299
223
|
)
|
|
300
224
|
await self.autoscaling_decisions_dict.put("current_replicas", actual_target_containers)
|
|
301
225
|
|
|
302
|
-
await self.cls.update_autoscaler(
|
|
226
|
+
await self.cls.update_autoscaler(
|
|
227
|
+
min_containers=actual_target_containers,
|
|
228
|
+
)
|
|
303
229
|
|
|
304
230
|
if time.time() - autoscaling_time < self.autoscaling_interval_seconds:
|
|
305
231
|
await asyncio.sleep(self.autoscaling_interval_seconds - (time.time() - autoscaling_time))
|
|
@@ -312,55 +238,7 @@ class _FlashPrometheusAutoscaler:
|
|
|
312
238
|
logger.error(traceback.format_exc())
|
|
313
239
|
await asyncio.sleep(self.autoscaling_interval_seconds)
|
|
314
240
|
|
|
315
|
-
async def
|
|
316
|
-
"""
|
|
317
|
-
Gets internal metrics from container to autoscale up or down.
|
|
318
|
-
"""
|
|
319
|
-
containers = await self._get_all_containers()
|
|
320
|
-
if len(containers) > current_replicas:
|
|
321
|
-
logger.info(
|
|
322
|
-
f"[Modal Flash] Current replicas {current_replicas} is less than the number of containers "
|
|
323
|
-
f"{len(containers)}. Setting current_replicas = num_containers."
|
|
324
|
-
)
|
|
325
|
-
current_replicas = len(containers)
|
|
326
|
-
|
|
327
|
-
if current_replicas == 0:
|
|
328
|
-
return 1
|
|
329
|
-
|
|
330
|
-
internal_metrics_list = []
|
|
331
|
-
for container in containers:
|
|
332
|
-
internal_metric = await self._get_container_metrics(container.task_id)
|
|
333
|
-
if internal_metric is None:
|
|
334
|
-
continue
|
|
335
|
-
internal_metrics_list.append(getattr(internal_metric.metrics, self.target_metric))
|
|
336
|
-
|
|
337
|
-
if not internal_metrics_list:
|
|
338
|
-
return current_replicas
|
|
339
|
-
|
|
340
|
-
avg_internal_metric = sum(internal_metrics_list) / len(internal_metrics_list)
|
|
341
|
-
|
|
342
|
-
scale_factor = avg_internal_metric / self.target_metric_value
|
|
343
|
-
|
|
344
|
-
desired_replicas = current_replicas
|
|
345
|
-
if scale_factor > 1 + self.scale_up_tolerance:
|
|
346
|
-
desired_replicas = math.ceil(current_replicas * scale_factor)
|
|
347
|
-
elif scale_factor < 1 - self.scale_down_tolerance:
|
|
348
|
-
desired_replicas = math.ceil(current_replicas * scale_factor)
|
|
349
|
-
|
|
350
|
-
logger.warning(
|
|
351
|
-
f"[Modal Flash] Current replicas: {current_replicas}, "
|
|
352
|
-
f"avg internal metric `{self.target_metric}`: {avg_internal_metric}, "
|
|
353
|
-
f"target internal metric value: {self.target_metric_value}, "
|
|
354
|
-
f"scale factor: {scale_factor}, "
|
|
355
|
-
f"desired replicas: {desired_replicas}"
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
desired_replicas = max(1, min(desired_replicas, self.max_containers or 1000))
|
|
359
|
-
return desired_replicas
|
|
360
|
-
|
|
361
|
-
async def _compute_target_containers_prometheus(self, current_replicas: int) -> int:
|
|
362
|
-
# current_replicas is the number of live containers + cold starting containers (not yet live)
|
|
363
|
-
# containers is the number of live containers that are registered in flash dns
|
|
241
|
+
async def _compute_target_containers(self, current_replicas: int) -> int:
|
|
364
242
|
containers = await self._get_all_containers()
|
|
365
243
|
if len(containers) > current_replicas:
|
|
366
244
|
logger.info(
|
|
@@ -375,7 +253,6 @@ class _FlashPrometheusAutoscaler:
|
|
|
375
253
|
target_metric = self.target_metric
|
|
376
254
|
target_metric_value = float(self.target_metric_value)
|
|
377
255
|
|
|
378
|
-
# Gets metrics from prometheus
|
|
379
256
|
sum_metric = 0
|
|
380
257
|
containers_with_metrics = 0
|
|
381
258
|
container_metrics_list = await asyncio.gather(
|
|
@@ -394,17 +271,11 @@ class _FlashPrometheusAutoscaler:
|
|
|
394
271
|
sum_metric += container_metrics[target_metric][0].value
|
|
395
272
|
containers_with_metrics += 1
|
|
396
273
|
|
|
397
|
-
# n_containers_missing_metric is the number of unhealthy containers + number of cold starting containers
|
|
398
274
|
n_containers_missing_metric = current_replicas - containers_with_metrics
|
|
399
|
-
# n_containers_unhealthy is the number of live containers that are not emitting metrics i.e. unhealthy
|
|
400
|
-
n_containers_unhealthy = len(containers) - containers_with_metrics
|
|
401
|
-
|
|
402
|
-
# Scale up assuming that every unhealthy container is at 2x the target metric value.
|
|
403
|
-
scale_up_target_metric_value = (sum_metric + n_containers_unhealthy * target_metric_value) / (
|
|
404
|
-
(containers_with_metrics + n_containers_unhealthy) or 1
|
|
405
|
-
)
|
|
406
275
|
|
|
407
|
-
# Scale down
|
|
276
|
+
# Scale up / down conservatively: Any container that is missing the metric is assumed to be at the minimum
|
|
277
|
+
# value of the metric when scaling up and the maximum value of the metric when scaling down.
|
|
278
|
+
scale_up_target_metric_value = sum_metric / current_replicas
|
|
408
279
|
scale_down_target_metric_value = (
|
|
409
280
|
sum_metric + n_containers_missing_metric * target_metric_value
|
|
410
281
|
) / current_replicas
|
|
@@ -419,14 +290,9 @@ class _FlashPrometheusAutoscaler:
|
|
|
419
290
|
desired_replicas = math.ceil(current_replicas * scale_down_ratio)
|
|
420
291
|
|
|
421
292
|
logger.warning(
|
|
422
|
-
f"[Modal Flash] Current replicas: {current_replicas}, "
|
|
423
|
-
f"
|
|
424
|
-
f"
|
|
425
|
-
f"number of containers with metrics: {containers_with_metrics}, "
|
|
426
|
-
f"number of containers unhealthy: {n_containers_unhealthy}, "
|
|
427
|
-
f"number of containers missing metric (includes unhealthy): {n_containers_missing_metric}, "
|
|
428
|
-
f"scale up ratio: {scale_up_ratio}, "
|
|
429
|
-
f"scale down ratio: {scale_down_ratio}, "
|
|
293
|
+
f"[Modal Flash] Current replicas: {current_replicas}, target metric value: {target_metric_value}, "
|
|
294
|
+
f"current sum of metric values: {sum_metric}, number of containers missing metric: "
|
|
295
|
+
f"{n_containers_missing_metric}, scale up ratio: {scale_up_ratio}, scale down ratio: {scale_down_ratio}, "
|
|
430
296
|
f"desired replicas: {desired_replicas}"
|
|
431
297
|
)
|
|
432
298
|
|
|
@@ -437,42 +303,20 @@ class _FlashPrometheusAutoscaler:
|
|
|
437
303
|
|
|
438
304
|
# Fetch the metrics from the endpoint
|
|
439
305
|
try:
|
|
440
|
-
response = await self.http_client.get(url
|
|
306
|
+
response = await self.http_client.get(url)
|
|
441
307
|
response.raise_for_status()
|
|
442
|
-
except asyncio.TimeoutError:
|
|
443
|
-
logger.warning(f"[Modal Flash] Timeout getting metrics from {url}")
|
|
444
|
-
return None
|
|
445
308
|
except Exception as e:
|
|
446
309
|
logger.warning(f"[Modal Flash] Error getting metrics from {url}: {e}")
|
|
447
310
|
return None
|
|
448
311
|
|
|
449
|
-
# Read body with timeout/error handling and parse Prometheus metrics
|
|
450
|
-
try:
|
|
451
|
-
text_body = await response.text()
|
|
452
|
-
except asyncio.TimeoutError:
|
|
453
|
-
logger.warning(f"[Modal Flash] Timeout reading metrics body from {url}")
|
|
454
|
-
return None
|
|
455
|
-
except Exception as e:
|
|
456
|
-
logger.warning(f"[Modal Flash] Error reading metrics body from {url}: {e}")
|
|
457
|
-
return None
|
|
458
|
-
|
|
459
312
|
# Parse the text-based Prometheus metrics format
|
|
460
313
|
metrics: dict[str, list[Sample]] = defaultdict(list)
|
|
461
|
-
for family in text_string_to_metric_families(
|
|
314
|
+
for family in text_string_to_metric_families(await response.text()):
|
|
462
315
|
for sample in family.samples:
|
|
463
316
|
metrics[sample.name] += [sample]
|
|
464
317
|
|
|
465
318
|
return metrics
|
|
466
319
|
|
|
467
|
-
async def _get_container_metrics(self, container_id: str) -> Optional[api_pb2.TaskGetAutoscalingMetricsResponse]:
|
|
468
|
-
req = api_pb2.TaskGetAutoscalingMetricsRequest(task_id=container_id)
|
|
469
|
-
try:
|
|
470
|
-
resp = await retry_transient_errors(self.client.stub.TaskGetAutoscalingMetrics, req)
|
|
471
|
-
return resp
|
|
472
|
-
except Exception as e:
|
|
473
|
-
logger.warning(f"[Modal Flash] Error getting metrics for container {container_id}: {e}")
|
|
474
|
-
return None
|
|
475
|
-
|
|
476
320
|
async def _get_all_containers(self):
|
|
477
321
|
req = api_pb2.FlashContainerListRequest(function_id=self.fn.object_id)
|
|
478
322
|
resp = await retry_transient_errors(self.client.stub.FlashContainerList, req)
|
|
@@ -551,14 +395,10 @@ async def flash_prometheus_autoscaler(
|
|
|
551
395
|
app_name: str,
|
|
552
396
|
cls_name: str,
|
|
553
397
|
# Endpoint to fetch metrics from. Must be in Prometheus format. Example: "/metrics"
|
|
554
|
-
# If metrics_endpoint is "internal", we will use containers' internal metrics to autoscale instead.
|
|
555
398
|
metrics_endpoint: str,
|
|
556
399
|
# Target metric to autoscale on. Example: "vllm:num_requests_running"
|
|
557
|
-
# If metrics_endpoint is "internal", target_metrics options are: [cpu_usage_percent, memory_usage_percent]
|
|
558
400
|
target_metric: str,
|
|
559
401
|
# Target metric value. Example: 25
|
|
560
|
-
# If metrics_endpoint is "internal", target_metric_value is a percentage value between 0.1 and 1.0 (inclusive),
|
|
561
|
-
# indicating container's usage of that metric.
|
|
562
402
|
target_metric_value: float,
|
|
563
403
|
min_containers: Optional[int] = None,
|
|
564
404
|
max_containers: Optional[int] = None,
|