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.
Files changed (190) hide show
  1. {modal-1.1.4 → modal-1.1.4.dev1}/PKG-INFO +1 -1
  2. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_clustered_functions.py +0 -3
  3. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_clustered_functions.pyi +2 -3
  4. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_functions.py +0 -11
  5. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/asgi.py +1 -1
  6. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/grpc_utils.py +0 -1
  7. {modal-1.1.4 → modal-1.1.4.dev1}/modal/app.py +2 -6
  8. {modal-1.1.4 → modal-1.1.4.dev1}/modal/app.pyi +0 -4
  9. {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2025.06.txt +0 -1
  10. {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/PREVIEW.txt +0 -1
  11. {modal-1.1.4 → modal-1.1.4.dev1}/modal/client.pyi +10 -2
  12. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cls.py +1 -6
  13. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cls.pyi +0 -16
  14. {modal-1.1.4 → modal-1.1.4.dev1}/modal/experimental/__init__.py +1 -2
  15. {modal-1.1.4 → modal-1.1.4.dev1}/modal/experimental/flash.py +23 -183
  16. {modal-1.1.4 → modal-1.1.4.dev1}/modal/experimental/flash.pyi +9 -83
  17. {modal-1.1.4 → modal-1.1.4.dev1}/modal/functions.pyi +0 -12
  18. {modal-1.1.4 → modal-1.1.4.dev1}/modal/image.py +2 -8
  19. {modal-1.1.4 → modal-1.1.4.dev1}/modal/image.pyi +4 -16
  20. {modal-1.1.4 → modal-1.1.4.dev1}/modal/mount.py +11 -17
  21. {modal-1.1.4 → modal-1.1.4.dev1}/modal/mount.pyi +0 -4
  22. {modal-1.1.4 → modal-1.1.4.dev1}/modal/parallel_map.py +4 -15
  23. {modal-1.1.4 → modal-1.1.4.dev1}/modal/parallel_map.pyi +0 -1
  24. {modal-1.1.4 → modal-1.1.4.dev1}/modal/sandbox.py +4 -31
  25. {modal-1.1.4 → modal-1.1.4.dev1}/modal/sandbox.pyi +3 -12
  26. {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/PKG-INFO +1 -1
  27. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api.proto +0 -29
  28. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_grpc.py +0 -32
  29. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_pb2.py +853 -893
  30. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_pb2.pyi +4 -90
  31. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_pb2_grpc.py +1 -68
  32. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/api_pb2_grpc.pyi +3 -25
  33. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/modal_api_grpc.py +0 -2
  34. {modal-1.1.4 → modal-1.1.4.dev1}/modal_version/__init__.py +1 -1
  35. {modal-1.1.4 → modal-1.1.4.dev1}/LICENSE +0 -0
  36. {modal-1.1.4 → modal-1.1.4.dev1}/README.md +0 -0
  37. {modal-1.1.4 → modal-1.1.4.dev1}/modal/__init__.py +0 -0
  38. {modal-1.1.4 → modal-1.1.4.dev1}/modal/__main__.py +0 -0
  39. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_container_entrypoint.py +0 -0
  40. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_ipython.py +0 -0
  41. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_location.py +0 -0
  42. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_object.py +0 -0
  43. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_output.py +0 -0
  44. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_partial_function.py +0 -0
  45. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_pty.py +0 -0
  46. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_resolver.py +0 -0
  47. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_resources.py +0 -0
  48. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/__init__.py +0 -0
  49. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/container_io_manager.py +0 -0
  50. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/container_io_manager.pyi +0 -0
  51. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/execution_context.py +0 -0
  52. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/execution_context.pyi +0 -0
  53. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/gpu_memory_snapshot.py +0 -0
  54. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/telemetry.py +0 -0
  55. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_runtime/user_code_imports.py +0 -0
  56. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_serialization.py +0 -0
  57. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_traceback.py +0 -0
  58. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_tunnel.py +0 -0
  59. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_tunnel.pyi +0 -0
  60. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_type_manager.py +0 -0
  61. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/__init__.py +0 -0
  62. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/app_utils.py +0 -0
  63. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/async_utils.py +0 -0
  64. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/auth_token_manager.py +0 -0
  65. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/blob_utils.py +0 -0
  66. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/bytes_io_segment_payload.py +0 -0
  67. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/deprecation.py +0 -0
  68. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/docker_utils.py +0 -0
  69. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/function_utils.py +0 -0
  70. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/git_utils.py +0 -0
  71. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/grpc_testing.py +0 -0
  72. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/hash_utils.py +0 -0
  73. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/http_utils.py +0 -0
  74. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/jwt_utils.py +0 -0
  75. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/logger.py +0 -0
  76. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/mount_utils.py +0 -0
  77. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/name_utils.py +0 -0
  78. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/package_utils.py +0 -0
  79. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/pattern_utils.py +0 -0
  80. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/rand_pb_testing.py +0 -0
  81. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/shell_utils.py +0 -0
  82. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_utils/time_utils.py +0 -0
  83. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_vendor/__init__.py +0 -0
  84. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_vendor/a2wsgi_wsgi.py +0 -0
  85. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_vendor/cloudpickle.py +0 -0
  86. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_vendor/tblib.py +0 -0
  87. {modal-1.1.4 → modal-1.1.4.dev1}/modal/_watcher.py +0 -0
  88. {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2023.12.312.txt +0 -0
  89. {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2023.12.txt +0 -0
  90. {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2024.04.txt +0 -0
  91. {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/2024.10.txt +0 -0
  92. {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/README.md +0 -0
  93. {modal-1.1.4 → modal-1.1.4.dev1}/modal/builder/base-images.json +0 -0
  94. {modal-1.1.4 → modal-1.1.4.dev1}/modal/call_graph.py +0 -0
  95. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/__init__.py +0 -0
  96. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/_download.py +0 -0
  97. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/_traceback.py +0 -0
  98. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/app.py +0 -0
  99. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/cluster.py +0 -0
  100. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/config.py +0 -0
  101. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/container.py +0 -0
  102. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/dict.py +0 -0
  103. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/entry_point.py +0 -0
  104. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/environment.py +0 -0
  105. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/import_refs.py +0 -0
  106. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/launch.py +0 -0
  107. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/network_file_system.py +0 -0
  108. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/profile.py +0 -0
  109. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/__init__.py +0 -0
  110. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/launch_instance_ssh.py +0 -0
  111. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/run_jupyter.py +0 -0
  112. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/run_marimo.py +0 -0
  113. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/programs/vscode.py +0 -0
  114. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/queues.py +0 -0
  115. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/run.py +0 -0
  116. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/secret.py +0 -0
  117. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/token.py +0 -0
  118. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/utils.py +0 -0
  119. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cli/volume.py +0 -0
  120. {modal-1.1.4 → modal-1.1.4.dev1}/modal/client.py +0 -0
  121. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cloud_bucket_mount.py +0 -0
  122. {modal-1.1.4 → modal-1.1.4.dev1}/modal/cloud_bucket_mount.pyi +0 -0
  123. {modal-1.1.4 → modal-1.1.4.dev1}/modal/config.py +0 -0
  124. {modal-1.1.4 → modal-1.1.4.dev1}/modal/container_process.py +0 -0
  125. {modal-1.1.4 → modal-1.1.4.dev1}/modal/container_process.pyi +0 -0
  126. {modal-1.1.4 → modal-1.1.4.dev1}/modal/dict.py +0 -0
  127. {modal-1.1.4 → modal-1.1.4.dev1}/modal/dict.pyi +0 -0
  128. {modal-1.1.4 → modal-1.1.4.dev1}/modal/environments.py +0 -0
  129. {modal-1.1.4 → modal-1.1.4.dev1}/modal/environments.pyi +0 -0
  130. {modal-1.1.4 → modal-1.1.4.dev1}/modal/exception.py +0 -0
  131. {modal-1.1.4 → modal-1.1.4.dev1}/modal/experimental/ipython.py +0 -0
  132. {modal-1.1.4 → modal-1.1.4.dev1}/modal/file_io.py +0 -0
  133. {modal-1.1.4 → modal-1.1.4.dev1}/modal/file_io.pyi +0 -0
  134. {modal-1.1.4 → modal-1.1.4.dev1}/modal/file_pattern_matcher.py +0 -0
  135. {modal-1.1.4 → modal-1.1.4.dev1}/modal/functions.py +0 -0
  136. {modal-1.1.4 → modal-1.1.4.dev1}/modal/gpu.py +0 -0
  137. {modal-1.1.4 → modal-1.1.4.dev1}/modal/io_streams.py +0 -0
  138. {modal-1.1.4 → modal-1.1.4.dev1}/modal/io_streams.pyi +0 -0
  139. {modal-1.1.4 → modal-1.1.4.dev1}/modal/network_file_system.py +0 -0
  140. {modal-1.1.4 → modal-1.1.4.dev1}/modal/network_file_system.pyi +0 -0
  141. {modal-1.1.4 → modal-1.1.4.dev1}/modal/object.py +0 -0
  142. {modal-1.1.4 → modal-1.1.4.dev1}/modal/object.pyi +0 -0
  143. {modal-1.1.4 → modal-1.1.4.dev1}/modal/output.py +0 -0
  144. {modal-1.1.4 → modal-1.1.4.dev1}/modal/partial_function.py +0 -0
  145. {modal-1.1.4 → modal-1.1.4.dev1}/modal/partial_function.pyi +0 -0
  146. {modal-1.1.4 → modal-1.1.4.dev1}/modal/proxy.py +0 -0
  147. {modal-1.1.4 → modal-1.1.4.dev1}/modal/proxy.pyi +0 -0
  148. {modal-1.1.4 → modal-1.1.4.dev1}/modal/py.typed +0 -0
  149. {modal-1.1.4 → modal-1.1.4.dev1}/modal/queue.py +0 -0
  150. {modal-1.1.4 → modal-1.1.4.dev1}/modal/queue.pyi +0 -0
  151. {modal-1.1.4 → modal-1.1.4.dev1}/modal/retries.py +0 -0
  152. {modal-1.1.4 → modal-1.1.4.dev1}/modal/runner.py +0 -0
  153. {modal-1.1.4 → modal-1.1.4.dev1}/modal/runner.pyi +0 -0
  154. {modal-1.1.4 → modal-1.1.4.dev1}/modal/running_app.py +0 -0
  155. {modal-1.1.4 → modal-1.1.4.dev1}/modal/schedule.py +0 -0
  156. {modal-1.1.4 → modal-1.1.4.dev1}/modal/scheduler_placement.py +0 -0
  157. {modal-1.1.4 → modal-1.1.4.dev1}/modal/secret.py +0 -0
  158. {modal-1.1.4 → modal-1.1.4.dev1}/modal/secret.pyi +0 -0
  159. {modal-1.1.4 → modal-1.1.4.dev1}/modal/serving.py +0 -0
  160. {modal-1.1.4 → modal-1.1.4.dev1}/modal/serving.pyi +0 -0
  161. {modal-1.1.4 → modal-1.1.4.dev1}/modal/snapshot.py +0 -0
  162. {modal-1.1.4 → modal-1.1.4.dev1}/modal/snapshot.pyi +0 -0
  163. {modal-1.1.4 → modal-1.1.4.dev1}/modal/stream_type.py +0 -0
  164. {modal-1.1.4 → modal-1.1.4.dev1}/modal/token_flow.py +0 -0
  165. {modal-1.1.4 → modal-1.1.4.dev1}/modal/token_flow.pyi +0 -0
  166. {modal-1.1.4 → modal-1.1.4.dev1}/modal/volume.py +0 -0
  167. {modal-1.1.4 → modal-1.1.4.dev1}/modal/volume.pyi +0 -0
  168. {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/SOURCES.txt +0 -0
  169. {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/dependency_links.txt +0 -0
  170. {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/entry_points.txt +0 -0
  171. {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/requires.txt +0 -0
  172. {modal-1.1.4 → modal-1.1.4.dev1}/modal.egg-info/top_level.txt +0 -0
  173. {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/__init__.py +0 -0
  174. {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/gen_cli_docs.py +0 -0
  175. {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/gen_reference_docs.py +0 -0
  176. {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/mdmd/__init__.py +0 -0
  177. {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/mdmd/mdmd.py +0 -0
  178. {modal-1.1.4 → modal-1.1.4.dev1}/modal_docs/mdmd/signatures.py +0 -0
  179. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/__init__.py +0 -0
  180. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/modal_options_grpc.py +0 -0
  181. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options.proto +0 -0
  182. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_grpc.py +0 -0
  183. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_pb2.py +0 -0
  184. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_pb2.pyi +0 -0
  185. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_pb2_grpc.py +0 -0
  186. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/options_pb2_grpc.pyi +0 -0
  187. {modal-1.1.4 → modal-1.1.4.dev1}/modal_proto/py.typed +0 -0
  188. {modal-1.1.4 → modal-1.1.4.dev1}/modal_version/__main__.py +0 -0
  189. {modal-1.1.4 → modal-1.1.4.dev1}/pyproject.toml +0 -0
  190. {modal-1.1.4 → modal-1.1.4.dev1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modal
3
- Version: 1.1.4
3
+ Version: 1.1.4.dev1
4
4
  Summary: Python client library for Modal
5
5
  Author-email: Modal Labs <support@modal.com>
6
6
  License: Apache-2.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, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str])"""
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, cluster_id: str, container_ips: list[str], container_ipv4_ips: list[str]) -> None:
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": 408})
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",
@@ -204,7 +204,6 @@ async def retry_transient_errors(
204
204
  else:
205
205
  total_deadline = None
206
206
 
207
- metadata = metadata + [("x-modal-timestamp", str(time.time()))]
208
207
  while True:
209
208
  attempt_metadata = [
210
209
  ("x-idempotency-key", idempotency_key),
@@ -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 for inputs and startup time in seconds.
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 for inputs and startup time in seconds.
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,
@@ -3,7 +3,6 @@ aiohttp==3.12.7
3
3
  aiosignal==1.3.2
4
4
  async-timeout==5.0.1 ; python_version < "3.11"
5
5
  attrs==25.3.0
6
- cbor2==5.7.0
7
6
  certifi==2025.4.26
8
7
  frozenlist==1.6.0
9
8
  grpclib==0.4.8
@@ -3,7 +3,6 @@ aiohttp==3.12.7
3
3
  aiosignal==1.3.2
4
4
  async-timeout==5.0.1 ; python_version < "3.11"
5
5
  attrs==25.3.0
6
- cbor2==5.7.0
7
6
  certifi==2025.4.26
8
7
  frozenlist==1.6.0
9
8
  grpclib==0.4.8
@@ -29,7 +29,11 @@ class _Client:
29
29
  _snapshotted: bool
30
30
 
31
31
  def __init__(
32
- self, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.1.4"
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, server_url: str, client_type: int, credentials: typing.Optional[tuple[str, str]], version: str = "1.1.4"
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, live_method
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, process=process, health_check_url=health_check_url)
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
- if self.metrics_endpoint == "internal":
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=} containers. "
293
- f" Autoscaling decision made in {time.time() - autoscaling_time} seconds."
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(min_containers=actual_target_containers)
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 _compute_target_containers_internal(self, current_replicas: int) -> int:
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 assuming that every container (including cold starting containers) are at the target metric value.
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"target metric value: {target_metric_value}, "
424
- f"current sum of metric values: {sum_metric}, "
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, timeout=3)
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(text_body):
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,