Modal は何をしますか?
概要
Modalは、AI/MLワークロードに焦点を当てた、Pythonコードをサーバーレスで実行するためのクラウドプラットフォームです。主な機能は以下の通りです。
- オンデマンドGPUコンピュート(T4、L4、A10、L40S、A100、H100、H200、B200)
- サーバーレス関数:ゼロから数千のコンテナへの自動スケーリング
- カスタムコンテナイメージ:すべてPythonコードで構築
- 永続ストレージ:モデル重みとデータセット用のVolumes
- Webエンドポイント:モデルとAPIの提供
- スケジュールジョブ:cronまたは固定間隔
- サブ秒のコールドスタート:低レイテンシ推論
Modalのすべてはコードとして定義されます。YAMLやDockerfileは不要ですが、両方ともサポートされています。
このスキルを使用するタイミング
以下の場合にこのスキルを使用します。
- AI/MLモデルをクラウドにデプロイまたは提供する場合
- GPUアクセラレーションを要する計算(トレーニング、推論、ファインチューニング)を実行する場合
- サーバーレスなWeb APIやエンドポイントを作成する場合
- バッチ処理ジョブを並列にスケーリングする場合
- 定期的なタスク(データパイプライン、再トレーニング、スクレイピング)をスケジュールする場合
- モデル重みやデータセット用の永続的なクラウドストレージが必要な場合
- カスタムコンテナ環境でコードを実行したい場合
- ジョブキューや非同期タスク処理システムを構築する場合
インストールと認証
インストール
uv pip install modal
Modal Python SDKはPython 3.10~3.14をサポートしています。このスキルは安定版のmodal>=1.0 API(現在のリリース:1.4.x)を対象としています。
認証
新しい認証情報を作成する前に、既存の認証情報を優先して使用してください。以下のModal固有の2つの変数のみが関連します。他の環境変数や.envファイルの内容を読み取ったり、公開したりしないでください。
MODAL_TOKEN_IDとMODAL_TOKEN_SECRETが現在の環境にすでに設定されているか確認します。- 設定されていない場合は、ローカルの
.envファイルでこれら2つのキーのみを検索し(他のエントリは無視)、ワークフローに適している場合は読み込みます。 - 上記のいずれのソースでもこれらの2つの値が提供されていない場合にのみ、インタラクティブな
modal setupにフォールバックするか、新しいトークンを生成します。
modal setup
ブラウザが開き認証が行われます。CI/CDやヘッドレス環境では、環境変数を使用します。
export MODAL_TOKEN_ID=<your-token-id>
export MODAL_TOKEN_SECRET=<your-token-secret>
トークンが環境または.envですでに利用可能でない場合は、https://modal.com/settings で生成します。
Modalは月額30ドルのクレジットが付与される無料枠を提供しています。
参考:詳細なセットアップと最初のアプリのチュートリアルについては、references/getting-started.mdを参照してください。
コアコンセプト
アプリと関数
ModalのAppは関連する関数をグループ化します。@app.function()でデコレートされた関数は、クラウド上でリモート実行されます。
import modal
app = modal.App("my-app")
@app.function()
def square(x):
return x ** 2
@app.local_entrypoint()
def main():
# .remote() はクラウド上で実行されます
print(square.remote(42))
modal run script.pyで実行します。modal deploy script.pyでデプロイします。
参考:ライフサイクルフック、クラス、.map()、.spawn()などについては、references/functions.mdを参照してください。
コンテナイメージ
ModalはPythonコードからコンテナイメージを構築します。推奨されるパッケージインストーラはuvです。
image = (
modal.Image.debian_slim(python_version="3.11")
.uv_pip_install("torch==2.12.0", "transformers==5.9.0", "accelerate==1.13.0")
.apt_install("git")
)
@app.function(image=image)
def inference(prompt):
from transformers import pipeline
pipe = pipeline("text-generation", model="meta-llama/Llama-3-8B")
return pipe(prompt)
主なイメージメソッド:
.uv_pip_install()— uvでPythonパッケージをインストール(推奨).pip_install()— pipでインストール(フォールバック).apt_install()— システムパッケージをインストール.run_commands()— ビルド中にシェルコマンドを実行.run_function()— ビルド中にPythonを実行(例:モデル重みのダウンロード).add_local_python_source()— ローカルモジュールを追加.env()— 環境変数を設定
参考:Dockerfile、micromamba、キャッシュ、GPUビルド手順については、references/images.mdを参照してください。
GPUコンピュート
gpuパラメータでGPUをリクエストします。
@app.function(gpu="H100")
def train_model():
import torch
device = torch.device("cuda")
# GPUトレーニングコード
# 複数GPU
@app.function(gpu="H100:4")
def distributed_training():
...
# GPUフォールバックチェーン
@app.function(gpu=["H100", "A100-80GB", "A100-40GB"])
def flexible_inference():
...
利用可能なGPU:T4、L4、A10、L40S、A100-40GB、A100-80GB、RTX-PRO-6000、H100、H200、B200、B200+
- GPUは常に文字列で指定します(例:
gpu="H100"、gpu="H100:4")。古いmodal.gpu.*オブジェクトはv0.73.31以降非推奨です。 - コンテナあたり最大8GPU(A10は最大4)
- L40Sは推論に推奨(コスト/パフォーマンスのバランス、48GB VRAM)
- H100/A100は追加費用なしでH200/A100-80GBに自動アップグレード可能
gpu="H100!"を使用すると自動アップグレードを防止
参考:GPU選択ガイダンスとマルチGPUトレーニングについては、references/gpu.mdを参照してください。
Volumes(永続ストレージ)
Volumesは分散型の永続ファイルストレージを提供します。
vol = modal.Volume.from_name("model-weights", create_if_missing=True)
@app.function(volumes={"/data": vol})
def save_model():
# マウントされたパスに書き込む
with open("/data/model.pt", "wb") as f:
torch.save(model.state_dict(), f)
@app.function(volumes={"/data": vol})
def load_model():
model.load_state_dict(torch.load("/data/model.pt"))
- 1回書き込み、多数回読み取りのワークロード(モデル重み、データセット)に最適化
- CLIアクセス:
modal volume ls、modal volume put、modal volume get - バックグラウンドで数秒ごとに自動コミット
vol.with_mount_options(read_only=True, sub_path="subset")で読み取り専用でマウント、またはサブディレクトリに制限
参考:v2 Volumes、同時書き込み、ベストプラクティスについては、references/volumes.mdを参照してください。
シークレット
関数に認証情報を安全に渡します。
@app.function(secrets=[modal.Secret.from_name("my-api-keys")])
def call_api():
import os
api_key = os.environ["API_KEY"]
# キーを使用
CLIでシークレットを作成:modal secret create my-api-keys API_KEY=sk-xxx
または.envファイルから:modal.Secret.from_dotenv()
参考:ダッシュボード設定、複数シークレット、テンプレートについては、references/secrets.mdを参照してください。
Webエンドポイント
モデルやAPIをWebエンドポイントとして提供します。
@app.function()
@modal.fastapi_endpoint()
def predict(text: str):
return {"result": model.predict(text)}
modal serve script.py— ホットリロードと一時的なURLを使用した開発modal deploy script.py— 永続的なURLを使用した本番デプロイ- FastAPI、ASGI(Starlette、FastHTML)、WSGI(Flask、Django)、WebSocketをサポート
- リクエスト本文は最大4GiB、レスポンスサイズは無制限
参考:ASGI/WSGIアプリ、ストリーミング、認証、WebSocketについては、references/web-endpoints.mdを参照してください。
スケジュールジョブ
スケジュールに従って関数を実行します。
@app.function(schedule=modal.Cron("0 9 * * *")) # 毎日UTC 9時
def daily_pipeline():
# ETL、再トレーニング、スクレイピングなど
...
@app.function(schedule=modal.Period(hours=6))
def periodic_check():
...
modal deploy script.pyでデプロイしてスケジュールを有効化します。
modal.Cron("...")— 標準のcron構文、デプロイ間で安定modal.Period(hours=N)— 固定間隔、再デプロイでリセット- 実行状況はModalダッシュボードで監視
参考:cron構文と管理については、references/scheduled-jobs.mdを参照してください。
スケーリングと並行性
Modalはコンテナを自動的にスケーリングします。制限を設定します。
@app.function(
max_containers=100, # 上限
min_containers=2, # 低レイテンシのために常に起動
buffer_containers=5, # 予備容量
scaledown_window=300, # シャットダウン前のアイドル秒数
)
def process(data):
...
.map()で入力を並列に処理します。
results = list(process.map([item1, item2, item3, ...]))
@modal.concurrentを使用して、コンテナごとの同時リクエスト処理を有効にします。target_inputs(オートスケーラーのコンテナあたりのターゲット数)をmax_inputs(ハードリミット)より低く設定して、スケールアップ中にヘッドルームを確保します。
@app.function()
@modal.concurrent(max_inputs=10, target_inputs=8)
async def handle_request(req):
...
デプロイ後に再デプロイせずに、呼び出し時にデプロイされた関数またはクラスを再設定するには、Function.with_options() / Function.with_concurrency() / Function.with_batching()(およびCls.with_options())を使用します。
Model = modal.Cls.from_name("my-app", "Model")
fast = Model.with_options(gpu="H200", max_containers=20)
fast().generate.remote(prompt)
参考:.map()、.starmap()、.spawn()、制限については、references/scaling.mdを参照してください。
リソース設定
@app.function(
cpu=4.0, # 物理コア(vCPUではありません)
memory=16384, # MiB
ephemeral_disk=51200, # MiB(最大3 TiB)
timeout=3600, # 秒
)
def heavy_computation():
...
デフォルト:0.125 CPUコア、128 MiBメモリ。最大(リクエスト、使用量)に基づいて課金されます。
参考:制限と課金の詳細については、references/resources.mdを参照してください。
ライフサイクルフックを持つクラス
ステートフルなワークロード(例:モデルを一度ロードして多くのリクエストを処理する)の場合:
@app.cls(gpu="L40S", image=image)
class Predictor:
@modal.enter()
def load_model(self):
self.model = load_heavy_model() # コンテナ起動時に一度だけ実行
@modal.method()
def predict(self, text: str):
return self.model(text)
@modal.exit()
def cleanup(self):
... # コンテナシャットダウン時に実行
次のように呼び出します:Predictor().predict.remote("hello")
サンドボックス
信頼できないコードや動的に生成されたコード(例:AIエージェントの出力やコードインタプリタ)を実行するには、modal.Sandboxを使用します。これはデコレートされた関数ではなく、プログラム的に作成および制御する隔離されたコンテナです。
app = modal.App.lookup("sandbox-demo", create_if_missing=True)
# 隔離されたコンテナ。信頼できないワークロードでは出力を制限
sb = modal.Sandbox.create(
app=app,
image=modal.Image.debian_slim(),
outbound_cidr_allowlist=["10.0.0.0/8"],
)
# ファイルシステムAPIを介してファイルをストリーム入出力(ベータ版)
sb.filesystem.write_text("print(2 ** 10)\n", "/tmp/job.py")
contents = sb.filesystem.read_text("/tmp/job.py")
sb.terminate()
- サンドボックス内でコマンドを実行するには、その
execメソッドを使用し(例:python /tmp/job.pyを実行)、返されたプロセスハンドルからstdoutを読み取ります。詳細はreferences/api_reference.mdを参照してください。 outbound_cidr_allowlist=[...]/inbound_cidr_allowlist=[...]で接続を制限sb.snapshot_filesystem()でファイルシステムをスナップショットして、ベースイメージとして再利用- コードインタプリタ、エージェントツール実行、ユーザーごとの分離に最適
一般的なワークフローパターン
GPUモデル推論サービス
import modal
app = modal.App("llm-service")
image = (
modal.Image.debian_slim(python_version="3.11")
.uv_pip_install("vllm")
)
@app.cls(gpu="H100", image=image, min_containers=1)
class LLMService:
@modal.enter()
def load(self):
from vllm import LLM
self.llm = LLM(model="meta-llama/Llama-3-70B")
@modal.method()
@modal.fastapi_endpoint(method="POST")
def generate(self, prompt: str, max_tokens: int = 256):
outputs = self.llm.generate([prompt], max_tokens=max_tokens)
return {"text": outputs[0].outputs[0].text}
バッチ処理パイプライン
app = modal.App("batch-pipeline")
vol = modal.Volume.from_name("pipeline-data", create_if_missing=True)
@app.function(volumes={"/data": vol}, cpu=4.0, memory=8192)
def process_chunk(chunk_id: int):
import pandas as pd
df = pd.read_parquet(f"/data/input/chunk_{chunk_id}.parquet")
result = heavy_transform(df)
result.to_parquet(f"/data/output/chunk_{chunk_id}.parquet")
return len(result)
@app.local_entrypoint()
def main():
chunk_ids = list(range(100))
results = list(process_chunk.map(chunk_ids))
print(f"処理完了: {sum(results)} 行")
スケジュールデータパイプライン
app = modal.App("etl-pipeline")
@app.function(
schedule=modal.Cron("0 */6 * * *"), # 6時間ごと
secrets=[modal.Secret.from_name("db-credentials")],
)
def etl_job():
import os
db_url = os.environ["DATABASE_URL"]
# 抽出、変換、ロード
...
CLIリファレンス
| コマンド | 説明 |
|---|---|
modal setup | Modalで認証 |
modal run script.py | スクリプトのローカルエントリポイントを実行 |
modal serve script.py | ホットリロード付き開発サーバー |
modal deploy script.py | 本番環境にデプロイ |
modal volume ls <name> | ボリューム内のファイル一覧表示 |
modal volume put <name> <file> | ファイルをボリュームにアップロード |
modal volume get <name> <file> | ボリュームからファイルをダウンロード |
modal secret create <name> K=V | シークレットを作成 |
modal secret list | シークレット一覧表示 |
modal app list | デプロイされたアプリ一覧表示 |
modal app stop <name> | デプロイされたアプリを停止 |
セキュリティ上の注意
- 認証情報: 認証に必要なのは
MODAL_TOKEN_IDとMODAL_TOKEN_SECRETのみです。他の環境変数や.envエントリを読み取ったり、ログに記録したり、転送したりしないでください。 - サブプロセス/カスタムサーバー: ここにある一部のパターン(マルチGPUトレーニングランチャー、
@modal.web_serverアプリ)は、ビルド中にsubprocess.run/subprocess.Popenまたはシェルコマンドを呼び出します。引数リストは固定してハードコードしてください。サニタイズされていないユーザー入力からサブプロセスまたはシェル引数を構築しないでください。信頼できない値はデータ(ファイル、環境変数、stdin)として渡し、コマンド引数として渡さないでください。 - 信頼できないコード: ユーザーまたはモデルが生成したコードは、通常の関数ではなく
modal.Sandbox(上記参照)内で実行し、CIDR許可リストでネットワークアクセスを制限してください。
リファレンスファイル
各トピックの詳細なドキュメント:
references/getting-started.md— インストール、認証、最初のアプリreferences/functions.md— 関数、クラス、ライフサイクルフック、リモート実行references/images.md— コンテナイメージ、パッケージインストール、キャッシュreferences/gpu.md— GPUタイプ
…
(翻訳のため一部省略)