mirror of
https://github.com/K-Dense-AI/claude-scientific-skills.git
synced 2026-03-29 07:43:46 +08:00
Update Modal skill
This commit is contained in:
@@ -1,274 +1,260 @@
|
||||
# Modal Functions
|
||||
# Modal Functions and Classes
|
||||
|
||||
## Basic Function Definition
|
||||
## Table of Contents
|
||||
|
||||
Decorate Python functions with `@app.function()`:
|
||||
- [Functions](#functions)
|
||||
- [Remote Execution](#remote-execution)
|
||||
- [Classes with Lifecycle Hooks](#classes-with-lifecycle-hooks)
|
||||
- [Parallel Execution](#parallel-execution)
|
||||
- [Async Functions](#async-functions)
|
||||
- [Local Entrypoints](#local-entrypoints)
|
||||
- [Generators](#generators)
|
||||
|
||||
## Functions
|
||||
|
||||
### Basic Function
|
||||
|
||||
```python
|
||||
import modal
|
||||
|
||||
app = modal.App(name="my-app")
|
||||
app = modal.App("my-app")
|
||||
|
||||
@app.function()
|
||||
def my_function():
|
||||
print("Hello from Modal!")
|
||||
return "result"
|
||||
def compute(x: int, y: int) -> int:
|
||||
return x + y
|
||||
```
|
||||
|
||||
## Calling Functions
|
||||
### Function Parameters
|
||||
|
||||
### Remote Execution
|
||||
The `@app.function()` decorator accepts:
|
||||
|
||||
Call `.remote()` to run on Modal:
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `image` | `Image` | Container image |
|
||||
| `gpu` | `str` | GPU type (e.g., `"H100"`, `"A100:2"`) |
|
||||
| `cpu` | `float` | CPU cores |
|
||||
| `memory` | `int` | Memory in MiB |
|
||||
| `timeout` | `int` | Max execution time in seconds |
|
||||
| `secrets` | `list[Secret]` | Secrets to inject |
|
||||
| `volumes` | `dict[str, Volume]` | Volumes to mount |
|
||||
| `schedule` | `Schedule` | Cron or periodic schedule |
|
||||
| `max_containers` | `int` | Max container count |
|
||||
| `min_containers` | `int` | Minimum warm containers |
|
||||
| `retries` | `int` | Retry count on failure |
|
||||
| `concurrency_limit` | `int` | Max concurrent inputs |
|
||||
| `ephemeral_disk` | `int` | Disk in MiB |
|
||||
|
||||
## Remote Execution
|
||||
|
||||
### `.remote()` — Synchronous Call
|
||||
|
||||
```python
|
||||
@app.local_entrypoint()
|
||||
def main():
|
||||
result = my_function.remote()
|
||||
print(result)
|
||||
result = compute.remote(3, 4) # Runs in the cloud, blocks until done
|
||||
```
|
||||
|
||||
### Local Execution
|
||||
|
||||
Call `.local()` to run locally (useful for testing):
|
||||
### `.local()` — Local Execution
|
||||
|
||||
```python
|
||||
result = my_function.local()
|
||||
result = compute.local(3, 4) # Runs locally (for testing)
|
||||
```
|
||||
|
||||
## Function Parameters
|
||||
|
||||
Functions accept standard Python arguments:
|
||||
### `.spawn()` — Async Fire-and-Forget
|
||||
|
||||
```python
|
||||
@app.function()
|
||||
def process(x: int, y: str):
|
||||
return f"{y}: {x * 2}"
|
||||
|
||||
@app.local_entrypoint()
|
||||
def main():
|
||||
result = process.remote(42, "answer")
|
||||
call = compute.spawn(3, 4) # Returns immediately
|
||||
# ... do other work ...
|
||||
result = call.get() # Retrieve result later
|
||||
```
|
||||
|
||||
## Deployment
|
||||
`.spawn()` supports up to 1 million pending inputs.
|
||||
|
||||
### Ephemeral Apps
|
||||
## Classes with Lifecycle Hooks
|
||||
|
||||
Run temporarily:
|
||||
```bash
|
||||
modal run script.py
|
||||
```
|
||||
|
||||
### Deployed Apps
|
||||
|
||||
Deploy persistently:
|
||||
```bash
|
||||
modal deploy script.py
|
||||
```
|
||||
|
||||
Access deployed functions from other code:
|
||||
Use `@app.cls()` for stateful workloads where you want to load resources once:
|
||||
|
||||
```python
|
||||
f = modal.Function.from_name("my-app", "my_function")
|
||||
result = f.remote(args)
|
||||
@app.cls(gpu="L40S", image=image)
|
||||
class Model:
|
||||
@modal.enter()
|
||||
def setup(self):
|
||||
"""Runs once when the container starts."""
|
||||
import torch
|
||||
self.model = torch.load("/weights/model.pt")
|
||||
self.model.eval()
|
||||
|
||||
@modal.method()
|
||||
def predict(self, text: str) -> dict:
|
||||
"""Callable remotely."""
|
||||
return self.model(text)
|
||||
|
||||
@modal.exit()
|
||||
def teardown(self):
|
||||
"""Runs when the container shuts down."""
|
||||
cleanup_resources()
|
||||
```
|
||||
|
||||
## Entrypoints
|
||||
### Lifecycle Decorators
|
||||
|
||||
### Local Entrypoint
|
||||
| Decorator | When It Runs |
|
||||
|-----------|-------------|
|
||||
| `@modal.enter()` | Once on container startup, before any inputs |
|
||||
| `@modal.method()` | For each remote call |
|
||||
| `@modal.exit()` | On container shutdown |
|
||||
|
||||
Code that runs on local machine:
|
||||
### Calling Class Methods
|
||||
|
||||
```python
|
||||
@app.local_entrypoint()
|
||||
def main():
|
||||
result = my_function.remote()
|
||||
print(result)
|
||||
# Create instance and call method
|
||||
model = Model()
|
||||
result = model.predict.remote("Hello world")
|
||||
|
||||
# Parallel calls
|
||||
results = list(model.predict.map(["text1", "text2", "text3"]))
|
||||
```
|
||||
|
||||
### Remote Entrypoint
|
||||
|
||||
Use `@app.function()` without local_entrypoint - runs entirely on Modal:
|
||||
### Parameterized Classes
|
||||
|
||||
```python
|
||||
@app.function()
|
||||
def train_model():
|
||||
# All code runs in Modal
|
||||
...
|
||||
```
|
||||
@app.cls()
|
||||
class Worker:
|
||||
model_name: str = modal.parameter()
|
||||
|
||||
Invoke with:
|
||||
```bash
|
||||
modal run script.py::app.train_model
|
||||
```
|
||||
@modal.enter()
|
||||
def load(self):
|
||||
self.model = load_model(self.model_name)
|
||||
|
||||
## Argument Parsing
|
||||
@modal.method()
|
||||
def run(self, data):
|
||||
return self.model(data)
|
||||
|
||||
Entrypoints with primitive type arguments get automatic CLI parsing:
|
||||
|
||||
```python
|
||||
@app.local_entrypoint()
|
||||
def main(foo: int, bar: str):
|
||||
some_function.remote(foo, bar)
|
||||
```
|
||||
|
||||
Run with:
|
||||
```bash
|
||||
modal run script.py --foo 1 --bar "hello"
|
||||
```
|
||||
|
||||
For custom parsing, accept variable-length arguments:
|
||||
|
||||
```python
|
||||
import argparse
|
||||
|
||||
@app.function()
|
||||
def train(*arglist):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--foo", type=int)
|
||||
args = parser.parse_args(args=arglist)
|
||||
```
|
||||
|
||||
## Function Configuration
|
||||
|
||||
Common parameters:
|
||||
|
||||
```python
|
||||
@app.function(
|
||||
image=my_image, # Custom environment
|
||||
gpu="A100", # GPU type
|
||||
cpu=2.0, # CPU cores
|
||||
memory=4096, # Memory in MB
|
||||
timeout=3600, # Timeout in seconds
|
||||
retries=3, # Number of retries
|
||||
secrets=[my_secret], # Environment secrets
|
||||
volumes={"/data": vol}, # Persistent storage
|
||||
)
|
||||
def my_function():
|
||||
...
|
||||
# Different model instances autoscale independently
|
||||
gpt = Worker(model_name="gpt-4")
|
||||
llama = Worker(model_name="llama-3")
|
||||
```
|
||||
|
||||
## Parallel Execution
|
||||
|
||||
### Map
|
||||
### `.map()` — Parallel Processing
|
||||
|
||||
Run function on multiple inputs in parallel:
|
||||
Process multiple inputs across containers:
|
||||
|
||||
```python
|
||||
@app.function()
|
||||
def evaluate_model(x):
|
||||
return x ** 2
|
||||
def process(item):
|
||||
return heavy_computation(item)
|
||||
|
||||
@app.local_entrypoint()
|
||||
def main():
|
||||
inputs = list(range(100))
|
||||
for result in evaluate_model.map(inputs):
|
||||
print(result)
|
||||
items = list(range(1000))
|
||||
results = list(process.map(items))
|
||||
print(f"Processed {len(results)} items")
|
||||
```
|
||||
|
||||
### Starmap
|
||||
- Results are returned in the same order as inputs
|
||||
- Modal autoscales containers to handle the workload
|
||||
- Use `return_exceptions=True` to collect errors instead of raising
|
||||
|
||||
For functions with multiple arguments:
|
||||
### `.starmap()` — Multi-Argument Parallel
|
||||
|
||||
```python
|
||||
@app.function()
|
||||
def add(a, b):
|
||||
return a + b
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
@app.local_entrypoint()
|
||||
def main():
|
||||
results = list(add.starmap([(1, 2), (3, 4)]))
|
||||
# [3, 7]
|
||||
results = list(add.starmap([(1, 2), (3, 4), (5, 6)]))
|
||||
# [3, 7, 11]
|
||||
```
|
||||
|
||||
### Exception Handling
|
||||
### `.map()` with `order_outputs=False`
|
||||
|
||||
For faster throughput when order doesn't matter:
|
||||
|
||||
```python
|
||||
results = my_func.map(
|
||||
range(3),
|
||||
return_exceptions=True,
|
||||
wrap_returned_exceptions=False
|
||||
)
|
||||
# [0, 1, Exception('error')]
|
||||
for result in process.map(items, order_outputs=False):
|
||||
handle(result) # Results arrive as they complete
|
||||
```
|
||||
|
||||
## Async Functions
|
||||
|
||||
Define async functions:
|
||||
Modal supports async/await natively:
|
||||
|
||||
```python
|
||||
@app.function()
|
||||
async def async_function(x: int):
|
||||
await asyncio.sleep(1)
|
||||
return x * 2
|
||||
|
||||
@app.local_entrypoint()
|
||||
async def main():
|
||||
result = await async_function.remote.aio(42)
|
||||
async def fetch_data(url: str) -> str:
|
||||
import httpx
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
return response.text
|
||||
```
|
||||
|
||||
## Generator Functions
|
||||
Async functions are especially useful with `@modal.concurrent()` for handling multiple requests per container.
|
||||
|
||||
Return iterators for streaming results:
|
||||
## Local Entrypoints
|
||||
|
||||
The `@app.local_entrypoint()` runs on your machine and orchestrates remote calls:
|
||||
|
||||
```python
|
||||
@app.local_entrypoint()
|
||||
def main():
|
||||
# This code runs locally
|
||||
data = load_local_data()
|
||||
|
||||
# These calls run in the cloud
|
||||
results = list(process.map(data))
|
||||
|
||||
# Back to local
|
||||
save_results(results)
|
||||
```
|
||||
|
||||
You can also define multiple entrypoints and select by function name:
|
||||
|
||||
```bash
|
||||
modal run script.py::train
|
||||
modal run script.py::evaluate
|
||||
```
|
||||
|
||||
## Generators
|
||||
|
||||
Functions can yield results as they're produced:
|
||||
|
||||
```python
|
||||
@app.function()
|
||||
def generate_data():
|
||||
for i in range(10):
|
||||
yield i
|
||||
for i in range(100):
|
||||
yield process(i)
|
||||
|
||||
@app.local_entrypoint()
|
||||
def main():
|
||||
for value in generate_data.remote_gen():
|
||||
print(value)
|
||||
for result in generate_data.remote_gen():
|
||||
print(result)
|
||||
```
|
||||
|
||||
## Spawning Functions
|
||||
## Retries
|
||||
|
||||
Submit functions for background execution:
|
||||
Configure automatic retries on failure:
|
||||
|
||||
```python
|
||||
@app.function()
|
||||
def process_job(data):
|
||||
# Long-running job
|
||||
return result
|
||||
|
||||
@app.local_entrypoint()
|
||||
def main():
|
||||
# Spawn without waiting
|
||||
call = process_job.spawn(data)
|
||||
|
||||
# Get result later
|
||||
result = call.get(timeout=60)
|
||||
@app.function(retries=3)
|
||||
def flaky_operation():
|
||||
...
|
||||
```
|
||||
|
||||
## Programmatic Execution
|
||||
|
||||
Run apps programmatically:
|
||||
For more control, use `modal.Retries`:
|
||||
|
||||
```python
|
||||
def main():
|
||||
with modal.enable_output():
|
||||
with app.run():
|
||||
result = some_function.remote()
|
||||
@app.function(retries=modal.Retries(max_retries=3, backoff_coefficient=2.0))
|
||||
def api_call():
|
||||
...
|
||||
```
|
||||
|
||||
## Specifying Entrypoint
|
||||
## Timeouts
|
||||
|
||||
With multiple functions, specify which to run:
|
||||
Set maximum execution time:
|
||||
|
||||
```python
|
||||
@app.function()
|
||||
def f():
|
||||
print("Function f")
|
||||
|
||||
@app.function()
|
||||
def g():
|
||||
print("Function g")
|
||||
@app.function(timeout=3600) # 1 hour
|
||||
def long_training():
|
||||
...
|
||||
```
|
||||
|
||||
Run specific function:
|
||||
```bash
|
||||
modal run script.py::app.f
|
||||
modal run script.py::app.g
|
||||
```
|
||||
Default timeout is 300 seconds (5 minutes). Maximum is 86400 seconds (24 hours).
|
||||
|
||||
Reference in New Issue
Block a user