Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Serverless GPU cloud platform for running ML workloads. Use when you need on-demand GPU access without infrastructure management, deploying ML models as APIs, or running batch jobs with automatic scaling.
.claude/skills/graniet-modal/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-12 | ✗→✓ | ▲ Improved | 231% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 495% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 72% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 117% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 68% | 0% |
This skill is repo-local and stays inactive until explicitly activated.
When the original instructions refer to legacy tool names, use these Kheish mappings:
terminal => bashweb_extract => web_fetch, plus web_search when discovery is neededsearch_files => grep_search and glob_searchbrowser_* tools require a browser-capable surfaced tool or MCP; if none is available, use the closest available surface and say so explicitlyWhen the instructions mention local helper files, resolve them from ${KHEISH_SKILL_DIR}.
Comprehensive guide to running ML workloads on Modal's serverless GPU cloud platform.
Use Modal when:
Key features:
Use alternatives instead:
bashpip install modal modal setup # Opens browser for authentication
pythonimport modal app = modal.App("hello-gpu") @app.function(gpu="T4") def gpu_info(): import subprocess return subprocess.run(["nvidia-smi"], capture_output=True, text=True).stdout @app.local_entrypoint() def main(): print(gpu_info.remote())
Run: modal run hello_gpu.py
pythonimport modal app = modal.App("text-generation") image = modal.Image.debian_slim().pip_install("transformers", "torch", "accelerate") @app.cls(gpu="A10G", image=image) class TextGenerator: @modal.enter() def load_model(self): from transformers import pipeline self.pipe = pipeline("text-generation", model="gpt2", device=0) @modal.method() def generate(self, prompt: str) -> str: return self.pipe(prompt, max_length=100)[0]["generated_text"] @app.local_entrypoint() def main(): print(TextGenerator().generate.remote("Hello, world"))
| Component | Purpose | |-----------|---------| | App | Container for functions and resources | | Function | Serverless function with compute specs | | Cls | Class-based functions with lifecycle hooks | | Image | Container image definition | | Volume | Persistent storage for models/data | | Secret | Secure credential storage |
| Command | Description | |---------|-------------| | modal run script.py | Execute and exit | | modal serve script.py | Development with live reload | | modal deploy script.py | Persistent cloud deployment |
| GPU | VRAM | Best For | |-----|------|----------| | T4 | 16GB | Budget inference, small models | | L4 | 24GB | Inference, Ada Lovelace arch | | A10G | 24GB | Training/inference, 3.3x faster than T4 | | L40S | 48GB | Recommended for inference (best cost/perf) | | A100-40GB | 40GB | Large model training | | A100-80GB | 80GB | Very large models | | H100 | 80GB | Fastest, FP8 + Transformer Engine | | H200 | 141GB | Auto-upgrade from H100, 4.8TB/s bandwidth | | B200 | Latest | Blackwell architecture |
python# Single GPU @app.function(gpu="A100") # Specific memory variant @app.function(gpu="A100-80GB") # Multiple GPUs (up to 8) @app.function(gpu="H100:4") # GPU with fallbacks @app.function(gpu=["H100", "A100", "L40S"]) # Any available GPU @app.function(gpu="any")
python# Basic image with pip image = modal.Image.debian_slim(python_version="3.11").pip_install( "torch==2.1.0", "transformers==4.36.0", "accelerate" ) # From CUDA base image = modal.Image.from_registry( "nvidia/cuda:12.1.0-cudnn8-devel-ubuntu22.04", add_python="3.11" ).pip_install("torch", "transformers") # With system packages image = modal.Image.debian_slim().apt_install("git", "ffmpeg").pip_install("whisper")
pythonvolume = modal.Volume.from_name("model-cache", create_if_missing=True) @app.function(gpu="A10G", volumes={"/models": volume}) def load_model(): import os model_path = "/models/llama-7b" if not os.path.exists(model_path): model = download_model() model.save_pretrained(model_path) volume.commit() # Persist changes return load_from_path(model_path)
python@app.function() @modal.fastapi_endpoint(method="POST") def predict(text: str) -> dict: return {"result": model.predict(text)}
pythonfrom fastapi import FastAPI web_app = FastAPI() @web_app.post("/predict") async def predict(text: str): return {"result": await model.predict.remote.aio(text)} @app.function() @modal.asgi_app() def fastapi_app(): return web_app
| Decorator | Use Case | |-----------|----------| | @modal.fastapi_endpoint() | Simple function → API | | @modal.asgi_app() | Full FastAPI/Starlette apps | | @modal.wsgi_app() | Django/Flask apps | | @modal.web_server(port) | Arbitrary HTTP servers |
python@app.function() @modal.batched(max_batch_size=32, wait_ms=100) async def batch_predict(inputs: list[str]) -> list[dict]: # Inputs automatically batched return model.batch_predict(inputs)
bash# Create secret modal secret create huggingface HF_TOKEN=hf_xxx
python@app.function(secrets=[modal.Secret.from_name("huggingface")]) def download_model(): import os token = os.environ["HF_TOKEN"]
python@app.function(schedule=modal.Cron("0 0 * * *")) # Daily midnight def daily_job(): pass @app.function(schedule=modal.Period(hours=1)) def hourly_job(): pass
python@app.function( container_idle_timeout=300, # Keep warm 5 min allow_concurrent_inputs=10, # Handle concurrent requests ) def inference(): pass
python@app.cls(gpu="A100") class Model: @modal.enter() # Run once at container start def load(self): self.model = load_model() # Load during warm-up @modal.method() def predict(self, x): return self.model(x)
python@app.function() def process_item(item): return expensive_computation(item) @app.function() def run_parallel(): items = list(range(1000)) # Fan out to parallel containers results = list(process_item.map(items)) return results
python@app.function( gpu="A100", memory=32768, # 32GB RAM cpu=4, # 4 CPU cores timeout=3600, # 1 hour max container_idle_timeout=120,# Keep warm 2 min retries=3, # Retry on failure concurrency_limit=10, # Max concurrent containers ) def my_function(): pass
python# Test locally if __name__ == "__main__": result = my_function.local() # View logs # modal app logs my-app
| Issue | Solution | |-------|----------| | Cold start latency | Increase container_idle_timeout, use @modal.enter() | | GPU OOM | Use larger GPU (A100-80GB), enable gradient checkpointing | | Image build fails | Pin dependency versions, check CUDA compatibility | | Timeout errors | Increase timeout, add checkpointing |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 14,393 | 11,516 | -20% | 1 | 1 | 0% | 2,216 | 3,809 | +72% | 0 | 0 | — |
case-02 | pass→pass | 9,160 | 3,515 | -62% | 1 | 1 | 0% | 1,471 | 3,190 | +117% | 0 | 0 | — |
case-03 | pass→pass | 13,038 | 6,803 | -48% | 1 | 1 | 0% | 2,154 | 3,627 | +68% | 0 | 0 | — |
case-04 | pass→pass | 2,757 | 1,574 | -43% | 1 | 1 | 0% | 435 | 2,828 | +550% | 0 | 0 | — |
case-05 | pass→pass | 2,204 | 1,941 | -12% | 1 | 1 | 0% | 350 | 2,869 | +720% | 0 | 0 | — |
case-06 | pass→pass | 4,897 | 2,433 | -50% | 1 | 1 | 0% | 987 | 3,015 | +205% | 0 | 0 | — |
case-07 | pass→pass | 3,527 | 1,715 | -51% | 1 | 1 | 0% | 597 | 2,894 | +385% | 0 | 0 | — |
case-08 | pass→pass | 9,630 | 1,749 | -82% | 1 | 1 | 0% | 1,614 | 2,882 | +79% | 0 | 0 | — |
case-09 | pass→pass | 3,659 | 2,646 | -28% | 1 | 1 | 0% | 617 | 3,126 | +407% | 0 | 0 | — |
case-18 | pass→pass | 3,814 | 1,945 | -49% | 1 | 1 | 0% | 664 | 2,872 | +333% | 0 | 0 | — |
case-10 | pass→pass | 3,537 | 1,775 | -50% | 1 | 1 | 0% | 676 | 2,885 | +327% | 0 | 0 | — |
case-11 | pass→pass | 4,405 | 3,088 | -30% | 1 | 1 | 0% | 713 | 3,161 | +343% | 0 | 0 | — |
case-12 | fail→pass | 4,978 | 2,205 | -56% | 1 | 1 | 0% | 897 | 2,966 | +231% | 0 | 0 | — |
case-13 | fail→pass | 2,838 | 1,861 | -34% | 1 | 1 | 0% | 501 | 2,981 | +495% | 0 | 0 | — |
case-14 | pass→pass | 4,028 | 2,453 | -39% | 1 | 1 | 0% | 768 | 3,045 | +296% | 0 | 0 | — |
case-15 | pass→pass | 4,296 | 3,315 | -23% | 1 | 1 | 0% | 818 | 3,139 | +284% | 0 | 0 | — |
case-16 | pass→pass | 4,023 | 2,224 | -45% | 1 | 1 | 0% | 743 | 3,006 | +305% | 0 | 0 | — |
case-17 | pass→pass | 3,034 | 2,347 | -23% | 1 | 1 | 0% | 570 | 3,000 | +426% | 0 | 0 | — |
case-19 | pass→pass | 3,582 | 2,135 | -40% | 1 | 1 | 0% | 687 | 2,963 | +331% | 0 | 0 | — |
case-20 | pass→pass | 2,726 | 2,358 | -13% | 1 | 1 | 0% | 433 | 2,991 | +591% | 0 | 0 | — |
case-21 | pass→pass | 8,471 | 3,952 | -53% | 1 | 1 | 0% | 1,606 | 3,390 | +111% | 0 | 0 | — |
case-22 | pass→pass | 2,911 | 1,430 | -51% | 1 | 1 | 0% | 554 | 2,820 | +409% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +9 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.