WASI 기반 AI 인퍼런스 최적화 – 모델 통합, 요청 시간 분석, 비용 절감 전략
앞선 시리즈에서는 WASI 환경에서 AI 추론 로직을 작성하고, 이를 서버리스 구조로 배포하는 방법까지 다뤘습니다. 이번에는 한 단계 더 나아가, 실제 딥러닝 모델을 WASI 구조에 통합하고, 성능과 비용 측면에서의 최적화 방안을 정리해보겠습니다.
WASM + WASI 기반 AI는 확장성과 유연성에서 큰 장점을 가지지만, 실제 서비스에 적용하려면 로딩 속도, 응답 시간, 실행 비용 등의 현실적인 과제를 해결해야 합니다. 이 글에서는 실제 모델을 통합하면서 마주치는 주요 이슈들을 중심으로, 각 요소를 최적화하는 전략을 다뤄보겠습니다.
1. 실제 모델 통합 구조 설계
WASI는 브라우저처럼 sandbox 환경이지만, 로컬 파일 접근이나 입출력은 허용합니다. 이를 활용해 WASM 내에 학습된 모델을 include_bytes!
또는 외부 경로로 포함시킬 수 있습니다.
Tract 기반 ONNX 추론 통합
tract
라이브러리는 CPU-only 환경에서도 ONNX 모델을 실행할 수 있어, WASI에서 사용하기에 적합합니다. 모델을 static으로 포함하거나 볼륨 마운트를 통해 동적으로 로드할 수 있습니다.
use tract_onnx::prelude::*;
fn load_model() -> TractResult<SimplePlan<TypedFact, Box<dyn TypedOp>>> {
let model_data = include_bytes!("../assets/mnist_quantized.onnx");
tract_onnx::onnx()
.model_for_read(&mut &model_data[..])?
.into_optimized()?
.into_runnable()
}
여기서 모델을 include하면 컴파일 타임에 바이너리에 포함되며, 별도의 파일 시스템 권한 설정 없이도 실행이 가능합니다. 다만, 파일 크기가 클 경우 컴파일 및 배포 용량에 영향을 주므로 양자화 모델 사용을 권장합니다.
2. 요청 시간 측정: 어디서 병목이 생기나?
WASI 기반 인퍼런스에서 병목은 크게 세 가지 지점에서 발생할 수 있습니다.
- Cold start 시간: WASM 런타임 초기화 및 모델 로딩
- 입력 파싱 시간: JSON/CSV/바이너리 등 형식에 따른 처리 지연
- 추론 시간: 모델 연산 자체의 연산 시간
Rust 코드 내 측정 방식
Rust 표준 라이브러리의 std::time::Instant
을 활용해 각 단계의 실행 시간을 측정할 수 있습니다.
let start = Instant::now();
let model = load_model()?; // 모델 로딩
let load_time = start.elapsed();
let infer_start = Instant::now();
let result = model.run(tvec!(input_tensor))?;
let infer_time = infer_start.elapsed();
println!("load: {:?}, infer: {:?}", load_time, infer_time);
측정된 시간은 로그로 출력하거나 API 응답에 포함하여 외부에서 모니터링할 수도 있습니다.
실측 예시 (로컬 WASI 런타임 기준)
- 모델 로딩: 약 80~150ms (include 방식)
- 추론 시간: 약 10~30ms (MNIST 양자화 기준)
- 전체 응답 시간: 평균 100~180ms
이 수치는 모델 복잡도, 런타임, 컴파일 옵션에 따라 크게 달라질 수 있으며, 서버리스 플랫폼에서는 cold start 시 수초까지도 발생할 수 있습니다.
3. 비용 절감을 위한 구조적 최적화
서버리스 WASM은 짧고 빈번한 요청에 강하지만, 비용을 고려하면 모든 요청을 단일 구조로 처리하는 것은 바람직하지 않습니다. 아래는 비용 절감을 위한 몇 가지 전략입니다.
1) 다단계 경량 모델 적용
복잡한 모델은 자주 호출되면 비용이 급격히 증가합니다. 이를 방지하기 위해, 경량 모델(예: 규칙 기반, SVM, 단순 CNN)로 1차 필터링한 뒤, 고정밀 모델은 조건부로 호출하는 구조가 효율적입니다.
// 1단계 필터
if simple_score < threshold {
return early_response;
}
// 필요 시 정밀 모델 추론 실행
let result = heavy_model.run(...);
2) 모델 pre-loading
매 요청마다 모델을 다시 로드하지 않고, 런타임 메모리에 모델을 유지할 수 있는 구조를 선택하면 cold start 비용을 줄일 수 있습니다. Fermyon Spin이나 Wasmtime Wagi에서 이를 지원합니다.
3) 요청 단순화 (압축, 버퍼 입력)
JSON보다 이진 포맷(CBOR, MsgPack) 또는 압축된 바이트 배열을 사용하는 방식은 입력 파싱 시간을 줄이고 네트워크 비용도 절감됩니다. 특히 이미지 입력 시 base64 인코딩보다 raw binary 형태가 훨씬 효율적입니다.
4. 실제 활용 예: 서버리스 숫자 인식 API
예를 들어 사용자가 모바일 앱에서 숫자 필기체를 그리면, 해당 이미지를 전처리해 서버리스 WASM API로 전송합니다. 서버는 다음과 같은 단계를 수행합니다.
- 입력 수신 (base64 → grayscale → 텐서 변환)
- 간단한 threshold 필터로 noise 제거
- WASM 내 tract 모델로 추론
- 예측 결과를 JSON으로 반환
이 구조는 서버나 GPU 없이도 수천 TPS까지 대응 가능하며, 모델만 충분히 경량화되어 있다면 브라우저보다 빠른 응답도 가능합니다.
마무리
이번 글에서는 실제 모델을 WASI 기반 구조에 통합하고, 성능 분석과 비용 최적화 전략까지 살펴보았습니다. 서버리스 WASM 환경에서의 AI 인퍼런스는 이미 실용적인 수준에 도달했으며, 앞으로는 WASM 런타임의 고도화와 함께 더욱 넓은 영역에서 활용될 수 있을 것입니다.
다음 시리즈에서는 WASM 기반 추론 서비스를 CI/CD로 자동 배포하고, 클라우드 로그/모니터링까지 연동하는 실제 운영 사례를 다뤄볼 예정입니다. 이제 개발과 배포, 운영까지 하나로 연결해봅시다.