modal은(는) 무엇을 하나요?
개요
Modal은 AI/ML 워크로드에 중점을 둔, Python 코드를 서버리스로 실행하기 위한 클라우드 플랫폼입니다. 주요 기능:
- GPU 컴퓨팅 주문형 (T4, L4, A10, L40S, A100, H100, H200, B200)
- 서버리스 함수 0에서 수천 개의 컨테이너로 자동 확장
- 사용자 정의 컨테이너 이미지 Python 코드로 완전히 빌드
- 영구 스토리지 모델 가중치 및 데이터 세트를 위한 Volume
- 웹 엔드포인트 모델 및 API 서빙
- 예약된 작업 cron 또는 고정 간격
- 1초 미만의 콜드 스타트 짧은 대기 시간 추론
Modal의 모든 것은 코드로 정의됩니다 — YAML이나 Dockerfile이 필요하지 않습니다 (둘 다 지원되지만).
이 스킬을 사용해야 할 때
다음과 같은 경우 이 스킬을 사용하세요:
- 클라우드에서 AI/ML 모델 배포 또는 서빙
- GPU 가속 컴퓨팅 실행 (학습, 추론, 미세 조정)
- 서버리스 웹 API 또는 엔드포인트 생성
- 배치 처리 작업 병렬 확장
- 반복 작업 예약 (데이터 파이프라인, 재학습, 스크래핑)
- 모델 가중치 또는 데이터 세트를 위한 영구 클라우드 스토리지 필요
- 사용자 정의 컨테이너 환경에서 코드 실행
- 작업 큐 또는 비동기 작업 처리 시스템 구축
설치 및 인증
설치
uv pip install modal
Modal Python SDK는 Python 3.10–3.14를 지원합니다. 이 스킬은 안정적인 modal>=1.0 API를 대상으로 합니다 (현재 릴리스: 1.4.x).
인증
새 자격 증명을 만들기 전에 기존 자격 증명을 선호합니다. 아래 두 가지 Modal 관련 변수만 관련이 있습니다 — 다른 환경 변수나 .env 파일 내용을 읽거나, 로드하거나, 노출하지 마세요:
MODAL_TOKEN_ID와MODAL_TOKEN_SECRET이 현재 환경에 이미 설정되어 있는지 확인합니다.- 그렇지 않은 경우, 로컬
.env파일에서 해당 두 키만 찾아보고 (다른 모든 항목은 무시) 워크플로에 적합하면 로드합니다. - 위 두 소스가 해당 두 값을 제공하지 않는 경우에만 대화형
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로 배포합니다.
참조: references/functions.md에서 생명 주기 훅, 클래스, .map(), .spawn() 등에 대해 알아보세요.
컨테이너 이미지
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()— 환경 변수 설정
참조: references/images.md에서 Dockerfiles, micromamba, 캐싱, GPU 빌드 단계에 대해 알아보세요.
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부터 사용 중단되었습니다. - 컨테이너당 최대 8개의 GPU (A10 제외: 최대 4개)
- L40S는 추론에 권장됩니다 (비용/성능 균형, 48GB VRAM)
- H100/A100은 추가 비용 없이 H200/A100-80GB로 자동 업그레이드될 수 있습니다.
- 자동 업그레이드를 방지하려면
gpu="H100!"를 사용하세요.
참조: references/gpu.md에서 GPU 선택 지침 및 다중 GPU 학습에 대해 알아보세요.
Volume (영구 스토리지)
Volume은 분산된 영구 파일 스토리지를 제공합니다:
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"))
- 한 번 쓰고 여러 번 읽는 워크로드에 최적화 (모델 가중치, 데이터 세트)
- CLI 액세스:
modal volume ls,modal volume put,modal volume get - 몇 초마다 백그라운드 자동 커밋
vol.with_mount_options(read_only=True, sub_path="subset")로 읽기 전용 마운트 또는 하위 디렉토리로 제한
참조: references/volumes.md에서 v2 volumes, 동시 쓰기 및 모범 사례에 대해 알아보세요.
시크릿
함수에 자격 증명을 안전하게 전달하세요:
@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에서 대시보드 설정, 여러 시크릿 및 템플릿에 대해 알아보세요.
웹 엔드포인트
웹 엔드포인트로 모델 및 API를 서빙하세요:
@app.function()
@modal.fastapi_endpoint()
def predict(text: str):
return {"result": model.predict(text)}
modal serve script.py— 핫 리로드가 있는 개발 및 임시 URLmodal deploy script.py— 영구 URL이 있는 프로덕션 배포- FastAPI, ASGI (Starlette, FastHTML), WSGI (Flask, Django), WebSockets 지원
- 요청 본문 최대 4 GiB, 응답 크기 무제한
참조: references/web-endpoints.md에서 ASGI/WSGI 앱, 스트리밍, 인증 및 WebSockets에 대해 알아보세요.
예약된 작업
일정에 따라 함수를 실행하세요:
@app.function(schedule=modal.Cron("0 9 * * *")) # 매일 오전 9시 UTC
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 대시보드에서 실행 모니터링
참조: references/scheduled-jobs.md에서 cron 구문 및 관리에 대해 알아보세요.
확장 및 동시성
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 또는 Cls를 재구성하려면
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)
참조: references/scaling.md에서 .map(), .starmap(), .spawn() 및 제한 사항에 대해 알아보세요.
리소스 구성
@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를 사용하세요 — 데코레이트된 Function이 아닌, 사용자가 생성하고 프로그래밍 방식으로 제어하는 격리된 컨테이너입니다:
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내에서 실행하고, 일반 Function에서는 실행하지 마세요. CIDR 허용 목록으로 네트워크 액세스를 제한하세요.
참조 파일
각 주제에 대한 자세한 문서:
references/getting-started.md— 설치, 인증, 첫 번째 앱references/functions.md— 함수, 클래스, 생명 주기 훅, 원격 실행references/images.md— 컨테이너 이미지, 패키지 설치, 캐싱references/gpu.md— GPU 유형
… (이하 번역 생략)