modal-parallel-execution
Skill4 starsChanged 3 months agoApache-2.0View on GitHub
Parallel execution patterns on Modal — map, starmap, spawn, spawn_map, generators, error handling, and retries. Use when running batch jobs in parallel, fan-out/fan-in patterns, fire-and-forget tasks, or streaming results from generators.
We split this skill into the rules it gives an agent
11 rules. About 0 are common, 5 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
Use this skill when running work in parallel across containers, using fan-out patterns, or handling errors and retries in distributed …
- Very specific: line 240
Use modal.Queue + modal.Function.spawn() for async job processing:
- Very specific: line 282
Use order_outputs=False and return_exceptions=True for resilient ingestion.
- Distinctive: line 292
Default timeout: 300s (5 min).
- Distinctive: line 298
Check if image build is the bottleneck
- Very specific: line 301
Add retries to handle transient failures
- Very specific: line 306
Ensure you call handle.get() to retrieve the result
- Very specific: line 307
Check if the Function errored (exceptions are raised on .get())
- Distinctive: line 311
Check if work is I/O-bound (consider async or concurrent inputs)
- Distinctive: line 329
Default timeout is 5 min — always set explicitly for long-running jobs
- Very specific: line 330
Use return_exceptions=True in .map() for resilient batch processing
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Modal Parallel Execution
Use this skill when running work in parallel across containers, using fan-out patterns, or handling errors and retries in distributed execution.
When to Use This Skill
- Running batch processing across many containers
- Fan-out / fan-in patterns
- Fire-and-forget background tasks
- Streaming results from remote generators
- Configuring retries for transient failures
- Processing large datasets in parallel
- Building job queues for async processing
- Dataset ingestion pipelines
Execution Patterns
.remote() — Single call
result = f.remote(arg)Synchronous remote call. Blocks until result returns.
.map() — Parallel batch
results = list(f.map([1, 2, 3, 4, 5]))Distributes inputs across containers in parallel. Results are ordered by default.
# Unordered (returns results as they complete)
results = list(f.map(inputs, order_outputs=False)).starmap() — Multi-argument parallel
results = list(f.starmap([(a1, b1), (a2, b2), (a3, b3)]))Each tuple is unpacked as positional arguments.
.spawn() — Fire-and-forget
handle = f.spawn(arg)
# ... do other work ...
result = handle.get() # retrieve when neededReturns a FunctionHandle for later retrieval. The Function runs asynchronously.
.spawn_map() — Async parallel batch
f.spawn_map(inputs) # creates inputs and returns immediately (does not wait for results)Like .map(), but exits as soon as inputs are created — useful for detached / fire-and-forget batch jobs (often with modal run --detach). Collect results later via another path (Volume, Dict, Queue, external store), or use .map() / .spawn() when you need handles or ordered results.
.remote_gen() — Remote generator
for chunk in f.remote_gen(arg):
print(chunk)Streams results from a generator Function.
Error Handling and Retries
Retries
@app.function(retries=modal.Retries(max_retries=3, backoff_coefficient=2.0))
def flaky_operation():
...Parameters:
max_retries— number of retry attemptsbackoff_coefficient— multiplier for wait time between retriesinitial_delay— seconds before first retry
Error handling in .map()
results = []
for result in f.map(inputs):
try:
results.append(result)
except Exception as e:
print(f"Failed: {e}")Exceptions from remote Functions are re-raised on the caller side as the original exception type.
With return_exceptions=True, failed inputs yield the exception object itself. As of SDK 1.4.0+, those exceptions are no longer wrapped in UserCodeException (the transitional wrap_returned_exceptions= parameter is deprecated).
Dynamic configuration (cross-link)
Per-invocation compute / concurrency / batching — see modal-scaling-performance:
f.with_options(gpu="H100").map(inputs)
f.with_concurrency(max_inputs=32).spawn_map(inputs)
f.with_batching(max_batch_size=64, wait_ms=100).remote(item)Generators
Modal Functions can be generators that yield results incrementally:
@app.function()
def process_large_file(path):
with open(path) as f:
for line in f:
yield transform(line)
# Consume remotely
for result in process_large_file.remote_gen("/data/big.csv"):
print(result)Real-World Patterns
Batch processing with map
@app.function()
def process_image(image_path):
return resize_and_upload(image_path)
@app.local_entrypoint()
def main():
image_paths = list_all_images()
results = list(process_image.map(image_paths))
print(f"Processed {len(results)} images")Fan-out with spawn
@app.local_entrypoint()
def main():
handles = [expensive_task.spawn(i) for i in range(100)]
results = [h.get() for h in handles]Pipeline chaining
@app.function()
def step1(data):
return preprocess(data)
@app.function(gpu="A100")
def step2(preprocessed):
return model_inference(preprocessed)
@app.local_entrypoint()
def pipeline():
preprocessed = list(step1.map(raw_data))
results = list(step2.map(preprocessed))Go SDK — Calling Deployed Functions
mc, _ := modal.NewClient()
fn, _ := mc.Functions.FromName(ctx, "my-app", "process_item", nil)
// Single remote call
result, _ := fn.Remote(ctx, []any{"arg1"}, nil)
// Parallel spawns (fan-out)
var calls []*modal.FunctionCall
for i := 0; i < 100; i++ {
call, _ := fn.Spawn(ctx, []any{i}, nil)
calls = append(calls, call)
}
// Collect results (fan-in)
for _, call := range calls {
result, _ := call.Get(ctx, nil)
fmt.Println(result)
}
// Autoscaler control
minC := uint32(2)
maxC := uint32(20)
fn.UpdateAutoscaler(ctx, &modal.FunctionUpdateAutoscalerParams{
MinContainers: &minC,
MaxContainers: &maxC,
})TypeScript SDK — Calling Deployed Functions
import { ModalClient } from "modal";
const modal = new ModalClient();
const fn = await modal.functions.fromName("my-app", "process_item");
// Single remote call
const result = await fn.remote(["arg1"]);
// Parallel spawns (fan-out)
const calls = await Promise.all(
Array.from({ length: 100 }, (_, i) => fn.spawn([i]))
);
// Collect results (fan-in)
const results = await Promise.all(calls.map(c => c.get()));
// Autoscaler control
await fn.updateAutoscaler({
minContainers: 2,
maxContainers: 20,
bufferContainers: 3,
});
// Class methods
const model = await modal.cls.fromName("my-app", "Model");
const instance = await model.create({ gpu: "A100" });
const prediction = await instance.method("predict").remote(["input"]);Job Queue Pattern
Use modal.Queue + modal.Function.spawn() for async job processing:
import modal
app = modal.App()
job_queue = modal.Queue.from_name("jobs", create_if_missing=True)
results = modal.Dict.from_name("results", create_if_missing=True)
@app.function()
def process_job(job_id: str, data: dict):
result = heavy_computation(data)
results[job_id] = result
return result
# Submit from a web endpoint
@app.function()
@modal.fastapi_endpoint(method="POST")
def submit(data: dict):
import uuid
job_id = str(uuid.uuid4())
process_job.spawn(job_id, data)
return {"job_id": job_id}Dataset Ingestion
For large-scale data processing:
@app.function()
def process_shard(shard_path: str):
import pyarrow.parquet as pq
table = pq.read_table(shard_path)
return transform(table)
@app.local_entrypoint()
def ingest():
shard_paths = list_s3_shards("s3://bucket/data/")
results = list(process_shard.map(shard_paths))Use order_outputs=False and return_exceptions=True for resilient ingestion.
Timeouts
@app.function(timeout=600) # max 10 min per invocation
def slow_task():
...Default timeout: 300s (5 min). Max: 86,400s (24h). Container-level timeout kills the process.
Symptom Triage
".map() is slow to start"
- Containers need to cold-start; use
min_containersorbuffer_containers - Check if image build is the bottleneck
"Some .map() inputs fail"
- Add
retriesto handle transient failures - Catch exceptions in the map loop
- Inspect per-input failures via
modal-observability(filter--function-call/--container)
".spawn() result never arrives"
- Ensure you call
handle.get()to retrieve the result - Check if the Function errored (exceptions are raised on
.get())
"Function times out"
- Increase
timeoutparameter - Check if work is I/O-bound (consider async or concurrent inputs)
Reference Map
references/map-starmap.md— parallel batch execution, spawn_map, ordering, error handlingreferences/spawn.md— fire-and-forget, FunctionHandle, async patternsreferences/generators.md— remote_gen, streaming, generator Functionsreferences/error-handling-retries.md— Retries, exception propagation, UserCodeException note- See also
modal-scaling-performanceforwith_options/with_concurrency/with_batching
Guardrails
.map()results are ordered by default; useorder_outputs=Falsefor speed.spawn()results must be explicitly retrieved with.get().spawn_map()does not wait for or return results — plan a result sink- Remote exceptions are re-raised on the caller side (not wrapped in
UserCodeExceptionon 1.4.0+) - Retries apply per-input, not per-container
- Generator Functions must
yield, notreturncollections - Default timeout is 5 min — always set explicitly for long-running jobs
- Use
return_exceptions=Truein.map()for resilient batch processing
In this repository80 other files it gives its agents.
- a11y-accessibility-designSkill
- agentic-ui-patternsSkill
- ai-native-ui-designSkill
- ai-ui-anti-patternsSkill
- ai-ui-heuristicsSkill
- calm-technology-uiSkill
- conversion-growth-designSkill
- cross-platform-designSkill
- dark-mode-designSkill
- data-visualizationSkill
- design-systemsSkill
- design-thinking-processSkill
- error-empty-statesSkill
- ethical-design-patternsSkill
- form-uxSkill
- information-architectureSkill
- interaction-design-patternsSkill
- internationalization-uiSkill
- mobile-touch-designSkill
- modal-classes-lifecycleSkill
- modal-container-imagesSkill
- modal-dicts-queuesSkill
- modal-endpointsSkill
- modal-fundamentalsSkill
- modal-gpu-computeSkill
- modal-networkingSkill
- modal-observabilitySkill
- modal-sandboxesSkill
- modal-scaling-performanceSkill
- modal-schedulingSkill
- modal-secretsSkill
- modal-serversSkill
- modal-storageSkill
- modal-web-servingSkill
- modal-workspace-adminSkill
- motion-design-principlesSkill
- notifications-permissionsSkill
- onboarding-uxSkill
- rust-api-designSkill
- rust-async-programmingSkill
- rust-cargo-masterySkill
- rust-cli-applicationsSkill
- rust-clippy-lintsSkill
- rust-closures-iteratorsSkill
- rust-compiler-internalsSkill
- rust-concurrencySkill
- rust-design-patternsSkill
- rust-editionsSkill
- rust-embeddedSkill
- rust-error-handlingSkill
- rust-game-developmentSkill
- rust-lifetimes-borrowingSkill
- rust-macrosSkill
- rust-performanceSkill
- rust-rustdocSkill
- rust-rustup-toolchainSkill
- rust-smart-pointersSkill
- rust-testing-benchmarkingSkill
- rust-type-systemSkill
- rust-unsafe-ffiSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

