Vercel에서 AI/ML 모델 서빙
Vercel에서 AI/ML 모델을 서빙하는 방법과 최적화 전략을 알아봅니다.
Vercel에서 AI/ML 모델 서빙
Introduction
AI/ML 모델을 웹 애플리케이션에서 서빙하는 것은 점점 더 중요해지고 있습니다. Vercel 환경에서 AI/ML 모델을 효율적으로 서빙하는 방법을 알아보겠습니다.
Environment
- Vercel 프로젝트 (Next.js 14+)
- TensorFlow.js
- ONNX Runtime
- Python 백엔드 (FastAPI)
Problem
Vercel에서 직접 ML 모델을 서빙할 때 발생하는 문제:
// 모델 로딩 시간이 너무 김
import * as tf from '@tensorflow/tfjs';
const model = await tf.loadLayersModel('model.json');
// Cold start 시 모델 로딩에 수십 초 소요Analysis
Vercel 환경에서 ML 모델 서빙의 도전:
- 메모리 제한: 서버리스 함수 메모리 제한
- Cold Start: 모델 로딩 시간
- CPU/GPU 제한: GPU 가속 불가
- 파일 시스템 접근 제한: 로컬 모델 파일 로딩 어려움
Solution
1. 사전 훈련된 모델 사용
// lib/model-loader.js
import * as tf from '@tensorflow/tfjs';
let model = null;
export async function loadModel() {
if (model) return model;
// 모델 로딩 (캐싱 활용)
model = await tf.loadLayersModel(
`${process.env.MODEL_URL}/model.json`
);
return model;
}
export async function predict(input) {
const loadedModel = await loadModel();
const tensor = tf.tensor2d([input]);
const prediction = loadedModel.predict(tensor);
const result = await prediction.data();
tensor.dispose();
prediction.dispose();
return Array.from(result);
}2. 외부 ML API 통합
// lib/ml-api.js
export async function callMLAPI(input, options = {}) {
const {
endpoint = process.env.ML_API_ENDPOINT,
timeout = 10000,
} = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ML_API_KEY}`,
},
body: JSON.stringify({ input }),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`ML API error: ${response.statusText}`);
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}3. Python 백엔드 서빙
# ml_service/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import numpy as np
import joblib
app = FastAPI()
# 모델 로딩
model = joblib.load('model.pkl')
scaler = joblib.load('scaler.pkl')
class PredictionRequest(BaseModel):
features: list[float]
class PredictionResponse(BaseModel):
prediction: float
confidence: float
@app.post("/predict", response_model=PredictionResponse)
async def predict(request: PredictionRequest):
try:
# 전처리
features = np.array(request.features).reshape(1, -1)
features_scaled = scaler.transform(features)
# 예측
prediction = model.predict(features_scaled)[0]
confidence = model.predict_proba(features_scaled).max()
return PredictionResponse(
prediction=float(prediction),
confidence=float(confidence)
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
async def health():
return {"status": "healthy"}4. Vercel에서 Python 백엔드 호출
// app/api/predict/route.js
import { NextResponse } from 'next/server';
export async function POST(request) {
const { features } = await request.json();
try {
const response = await fetch(`${process.env.ML_SERVICE_URL}/predict`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ features }),
});
const result = await response.json();
return NextResponse.json(result);
} catch (error) {
console.error('Prediction failed:', error);
return NextResponse.json(
{ error: 'Prediction failed' },
{ status: 500 }
);
}
}5. 모델 캐싱
// lib/model-cache.js
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
});
export async function getCachedPrediction(input, predictFn) {
const cacheKey = `prediction:${JSON.stringify(input)}`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const result = await predictFn(input);
await redis.setex(cacheKey, 3600, JSON.stringify(result));
return result;
}6. 스트리밍 응답
// app/api/stream-predict/route.js
import { NextResponse } from 'next/server';
export async function POST(request) {
const { input } = await request.json();
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
// 청크 단위로 예측 결과 전송
for (let i = 0; i < input.length; i += 100) {
const chunk = input.slice(i, i + 100);
const prediction = await predict(chunk);
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(prediction)}\n\n`)
);
}
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
controller.close();
} catch (error) {
controller.error(error);
}
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
},
});
}Lessons Learned
- 외부 API 활용: 복잡한 ML 모델은 외부 API를 통해 서빙하는 것이 효과적입니다.
- 캐싱 전략: 동일한 입력에 대한 예측은 캐싱하여 성능을 향상시킬 수 있습니다.
- 스트리밍: 대용량 예측은 스트리밍으로 처리하면 사용자 경험을 개선할 수 있습니다.
- 에러 핸들링: ML 서비스 실패 시 대체 로직이 필요합니다.
- 비용 고려: ML API 사용량에 따른 비용을 고려해야 합니다.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.