Modal
目次
ML の処理やモデルの API を、サーバーの管理なしで GPU に載せられるクラウドです。
skill の情報
| 提供元 | 追加インストール — hermes skills install official/mlops/modal で導入します |
| パス | optional-skills/mlops/modal |
| バージョン | 1.0.1 |
| 作者 | Orchestra Research |
| ライセンス | MIT |
| 依存関係 | modal>=1.0 |
| 対応プラットフォーム | linux, macos, windows |
| タグ | Infrastructure, Serverless, GPU, Cloud, Deployment, Modal |
参考: SKILL.md 全文
Modal のサーバーレス GPU
Modal のサーバーレス GPU クラウドで ML の処理を動かすための案内です。
Modal を使う場面
次のようなときに向いています:
- 基盤の面倒を見ずに、GPU を多く使う ML の処理を動かしたい
- ML のモデルを、自動で増減する API として公開したい
- まとめて動かす処理(学習、推論、データ加工)を回したい
- 待機中の費用をかけずに、秒単位で GPU の料金を払いたい
- ML のアプリをすばやく試作したい
- 決まった時刻に動く処理(cron のようなもの)を回したい
主な特長:
- サーバーレスの GPU: T4、L4、A10G、L40S、A100、H100、H200、B200 を必要なときに
- Python がそのまま設定になる: YAML なしで、基盤を Python のコードで書けます
- 自動での増減: ゼロまで減らせて、100 GPU 以上にもすぐ増やせます
- 1 秒未満で立ち上がる: Rust で作られた基盤により、コンテナの起動が速いです
- コンテナの使い回し: イメージの層が保存され、手直しのたびに待たされません
- Web の窓口: 関数をそのまま REST API として公開でき、更新中も止まりません
こちらのほうが向いている場合:
- RunPod: 状態を保ったまま長く動かしたいとき
- Lambda Labs: GPU インスタンスを確保しておきたいとき
- SkyPilot: 複数のクラウドをまたいで安く回したいとき
- Kubernetes: 複雑で多くのサービスからなる構成のとき
すぐ試す
導入
pip install modal
modal setup # Opens browser for authenticationGPU で Hello World
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())実行するには modal run hello_gpu.py と打ちます。
推論の窓口を作る
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"))中心となる考え方
主な部品
| 部品 | 役割 |
|---|---|
App |
関数や資源をまとめる入れ物 |
Function |
計算資源の指定を持つサーバーレスの関数 |
Cls |
起動時の処理などを持てる、クラス形式の関数 |
Image |
コンテナイメージの定義 |
Volume |
モデルやデータを残しておく保存領域 |
Secret |
秘密の情報を安全に置く場所 |
実行のしかた
| コマンド | 説明 |
|---|---|
modal run script.py |
実行して終わります |
modal serve script.py |
変更を反映しながら開発します |
modal deploy script.py |
クラウドに常設で置きます |
GPU の指定
使える GPU
| GPU | VRAM | 向いている用途 |
|---|---|---|
T4 |
16GB | 費用を抑えた推論、小さなモデル |
L4 |
24GB | 推論、Ada Lovelace 世代 |
A10G |
24GB | 学習と推論、T4 の 3.3 倍の速さ |
L40S |
48GB | 推論におすすめ(費用と性能の釣り合いがよい) |
A100-40GB |
40GB | 大きなモデルの学習 |
A100-80GB |
80GB | とても大きなモデル |
H100 |
80GB | 最速、FP8 と Transformer Engine に対応 |
H200 |
141GB | H100 から自動で切り替わる、4.8TB/s の帯域 |
B200 |
最新 | Blackwell 世代 |
GPU の書き方
# 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")コンテナイメージ
# 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")残しておける保存領域
volume = 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)Web の窓口
FastAPI の窓口をつける
@app.function()
@modal.fastapi_endpoint(method="POST")
def predict(text: str) -> dict:
return {"result": model.predict(text)}ASGI のアプリをまるごと載せる
from 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窓口の種類
| デコレータ | 使いどころ |
|---|---|
@modal.fastapi_endpoint() |
関数をそのまま API にする |
@modal.asgi_app() |
FastAPI や Starlette のアプリ全体 |
@modal.wsgi_app() |
Django や Flask のアプリ |
@modal.web_server(port) |
任意の HTTP サーバー |
自動でまとめて処理する
@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)秘密の情報の管理
# Create secret
modal secret create huggingface HF_TOKEN=hf_xxx@app.function(secrets=[modal.Secret.from_name("huggingface")])
def download_model():
import os
token = os.environ["HF_TOKEN"]決まった時刻に動かす
@app.function(schedule=modal.Cron("0 0 * * *")) # Daily midnight
def daily_job():
pass
@app.function(schedule=modal.Period(hours=1))
def hourly_job():
pass速くするために
立ち上がりの遅さを減らす
# Modal 1.0 autoscaler params: scaledown_window (was container_idle_timeout).
# Input concurrency moved to the @modal.concurrent decorator.
@app.function(scaledown_window=300) # Keep warm 5 min
@modal.concurrent(max_inputs=10) # Handle concurrent requests per container
def inference():
passモデルの読み込みのコツ
@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)並行して処理する
@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よく使う設定
@app.function(
gpu="A100",
memory=32768, # 32GB RAM
cpu=4, # 4 CPU cores
timeout=3600, # 1 hour max
scaledown_window=120, # Keep warm 2 min (was container_idle_timeout)
retries=3, # Retry on failure
max_containers=10, # Max concurrent containers (was concurrency_limit)
min_containers=1, # Keep N containers warm (was keep_warm)
)
def my_function():
pass> Modal 1.0 で変わった自動増減の設定名(移行の案内を参照してください): > - container_idle_timeout → scaledown_window > - concurrency_limit → max_containers > - keep_warm → min_containers > - allow_concurrent_inputs=N → @modal.concurrent(max_inputs=N) デコレータ
動きを調べる
# Test locally
if __name__ == "__main__":
result = my_function.local()
# View logs
# modal app logs my-app困ったとき
| 症状 | 対処 |
|---|---|
| 立ち上がりに時間がかかる | scaledown_window を長くし、@modal.enter() を使います |
| GPU のメモリが足りない | 大きな GPU(A100-80GB)に変え、勾配チェックポイントを有効にします |
| イメージの構築に失敗する | 依存関係の版を固定し、CUDA との組み合わせを確認します |
| 時間切れになる | timeout を延ばし、途中経過の保存を入れます |
参考資料
- Advanced Usage - 複数 GPU、分散学習、費用の最適化
- Troubleshooting - よくある症状と対処
参考リンク
- ドキュメント: https://modal.com/docs
- 例: https://github.com/modal-labs/modal-examples
- 料金: https://modal.com/pricing
- Discord: https://discord.gg/modal