인공지능(AI) 모델을 개발하는 과정과 이를 실제 비즈니스 환경에 적용하여 서비스하는 것은 완전히 다른 차원의 기술적 역량을 요구합니다. 수많은 데이터 사이언티스트들이 Jupyter Notebook 환경에서 높은 정확도의 모델을 만들어내는 데에는 능숙하지만, 막상 이 모델을 웹 서비스나 모바일 애플리케이션에서 실시간으로 호출할 수 있는 API 형태로 변환하는 과정(MLOps의 핵심)에서는 큰 난관에 봉착하곤 합니다.
초기 단계에서는 단순히 Flask, Django, 혹은 FastAPI와 같은 웹 프레임워크로 모델을 감싸서(Wrapping) 배포하는 방식을 시도할 수 있습니다. 하지만 서비스의 트래픽이 급증하거나, 모델의 버전을 지속적으로 관리해야 하거나, GPU 자원을 효율적으로 활용해야 하는 상황이 오면 이러한 방식은 금방 한계에 부딪히게 됩니다. 이때 필요한 것이 바로 구글에서 개발하여 오픈소스로 공개한 고성능 서빙 시스템인 TensorFlow Serving입니다.
이 글에서는 프로덕션 레벨의 AI 서비스를 구축하기 위한 TensorFlow Serving 사용법을 기초부터 심화까지 단계별로 상세히 알아보고, 모델 저장부터 Docker를 활용한 서버 구동, 그리고 클라이언트에서 API를 호출하는 방법까지 완벽하게 정리해 드리겠습니다. 이 가이드를 통해 여러분의 모델을 실험실에서 꺼내어 세상과 소통하게 만들어 보세요.
1. TensorFlow Serving이란 무엇이며 왜 필요한가?
TensorFlow Serving은 머신러닝 모델을 운영 환경(Production)에서 유연하고 고성능으로 서빙하기 위해 설계된 유연한 고성능 서빙 시스템입니다. 이는 단순히 모델을 메모리에 로드하여 추론(Inference) 결과를 반환하는 기능을 넘어, 엔터프라이즈 환경에서 필수적인 다음과 같은 강력한 기능들을 제공합니다.
- 무중단 버전 관리 (Model Versioning): 새로운 모델을 배포할 때 서버를 재시작하거나 중단할 필요가 없습니다. TensorFlow Serving은 파일 시스템을 모니터링하다가 새로운 버전이 감지되면 자연스럽게 모델을 교체하거나, 설정에 따라 여러 버전을 동시에 서빙하여 A/B 테스트를 가능하게 합니다.
- 표준화된 API 제공: 별도의 웹 서버 코드를 작성하지 않아도, 모델을 로드하는 것만으로 gRPC와 RESTful API 엔드포인트를 자동으로 생성합니다. 이는 백엔드 개발자와의 협업 효율성을 극대화합니다.
- 높은 처리량과 효율성 (High Throughput): 개별적으로 들어오는 요청을 일정 시간 동안 모아서 한 번에 처리하는 배칭(Batching) 기능을 내장하고 있습니다. 이를 통해 GPU나 TPU와 같은 하드웨어 가속기의 연산 효율을 극대화할 수 있습니다.
- 유연한 확장성: TensorFlow 모델뿐만 아니라, 확장 기능을 통해 다른 프레임워크의 모델이나 임베딩 데이터 등 다양한 유형의 서빙이 가능합니다.
따라서 TensorFlow Serving 사용법을 익히는 것은 단순한 도구 사용법을 넘어, MLOps 엔지니어로서의 필수 역량을 갖추는 첫걸음이라 할 수 있습니다.
2. 사전 준비: 모델을 SavedModel 형식으로 저장하기
TensorFlow Serving을 사용하기 위한 가장 첫 번째 단계는 모델을 올바른 형식으로 저장하는 것입니다. 우리가 학습 중간에 가중치를 저장하기 위해 흔히 사용하는 H5 포맷(.h5)이나 Keras 포맷(.keras)은 모델의 아키텍처와 가중치 정보만 포함하는 경우가 많습니다. 하지만 서빙을 위해서는 SavedModel 포맷을 사용해야 합니다.
SavedModel 포맷은 모델의 연산 그래프(Graph)와 서명(Signature) 정보를 모두 포함하고 있어, 서빙 서버가 별도의 Python 코드 없이도 모델의 입출력 구조를 완벽하게 이해할 수 있게 해줍니다.
SavedModel 저장 코드 예시
아래는 학습된 모델을 TensorFlow Serving이 인식할 수 있는 구조로 저장하는 Python 코드 예제입니다.
import tensorflow as tf import os # 1. 예시 모델 생성 및 학습 # 실제 현업에서는 학습이 완료된 모델 객체를 로드하여 사용하게 됩니다. model = tf.keras.Sequential([ tf.keras.layers.Dense(10, input_shape=(5,), activation='relu'), tf.keras.layers.Dense(1, activation='sigmoid') ]) model.compile(optimizer='adam', loss='binary_crossentropy') # 2. 모델 저장 경로 설정 # 주의: TensorFlow Serving은 버전 관리를 위해 정수형 폴더명을 요구합니다. MODEL_DIR = './my_model' version = 1 export_path = os.path.join(MODEL_DIR, str(version)) # 3. 모델 저장 (SavedModel 포맷) # tf.saved_model.save 함수를 사용하면 서명(Signature)이 포함된 형태로 저장됩니다. tf.saved_model.save(model, export_path) print(f"모델이 {export_path}에 성공적으로 저장되었습니다.")
위 코드를 실행하면 my_model/1/ 디렉토리 아래에 saved_model.pb 파일(그래프 정의)과 variables 폴더(가중치)가 생성됩니다. 폴더 구조가 모델명/버전번호/모델파일 형태를 갖추는 것이 매우 중요합니다. TensorFlow Serving은 이 디렉토리 구조를 스캔하여 가장 높은 숫자의 버전을 자동으로 최신 모델로 인식하고 로드하기 때문입니다.
3. Docker를 이용한 TensorFlow Serving 설치 및 실행
TensorFlow Serving을 설치하는 방법은 여러 가지가 있지만, 가장 간편하고 표준화된 방법은 Docker를 사용하는 것입니다. 리눅스 환경에 직접 바이너리를 설치할 수도 있지만, 의존성 충돌 문제와 환경 설정의 복잡함을 피하기 위해 Docker 컨테이너 사용을 강력히 추천합니다.
3.1 Docker 이미지 다운로드
먼저 터미널(또는 명령 프롬프트)에서 아래 명령어를 입력하여 구글이 제공하는 최신 TensorFlow Serving 이미지를 다운로드합니다.
docker pull tensorflow/serving
3.2 TensorFlow Serving 컨테이너 실행 (핵심)
이제 앞서 저장한 모델 폴더를 컨테이너에 마운트(Mount)하고 서버를 실행해 보겠습니다. 아래 명령어는 TensorFlow Serving 사용법의 핵심이므로 각 옵션의 의미를 정확히 이해해야 합니다.
docker run -t --rm -p 8501:8501 \ -v "$(pwd)/my_model:/models/my_model" \ -e MODEL_NAME=my_model \ tensorflow/serving
명령어 상세 분석:
docker run: 컨테이너를 실행하는 기본 명령어입니다.-t: 터미널 출력을 보기 위한 옵션입니다.--rm: 컨테이너가 중지되면 자동으로 삭제하여 불필요한 리소스 낭비를 막습니다.-p 8501:8501: 포트 포워딩 설정입니다. 호스트(내 컴퓨터)의 8501 포트와 컨테이너 내부의 8501 포트(REST API용)를 연결합니다. 만약 gRPC를 사용하려면-p 8500:8500을 추가해야 합니다.-v "$(pwd)/my_model:/models/my_model": 볼륨 마운트 설정입니다. 로컬의my_model폴더를 컨테이너 내부의/models/my_model경로에 연결합니다. 이를 통해 컨테이너가 내 컴퓨터에 있는 모델 파일에 접근할 수 있게 됩니다.-e MODEL_NAME=my_model: 환경 변수를 설정합니다. 여기서 지정한 이름이 나중에 API URL의 일부가 됩니다.
명령어가 정상적으로 실행되면 터미널에 로그가 출력되며, Running gRPC server on port 8500 및 Exporting HTTP/REST API at: ... 8501과 같은 메시지를 확인할 수 있습니다. 이제 서버가 요청을 받을 준비가 되었습니다.
4. REST API로 모델 호출하기 (클라이언트 구현)
서버가 구동되었으니 이제 실제로 데이터를 보내고 예측 결과를 받아볼 차례입니다. REST API는 HTTP 프로토콜을 사용하므로 curl, Postman, 또는 Python의 requests 라이브러리 등 다양한 도구로 쉽게 테스트할 수 있습니다.
4.1 API 엔드포인트 구조
기본적인 예측(Prediction) 요청 URL 구조는 다음과 같습니다. 여기서 {모델이름} 부분은 Docker 실행 시 MODEL_NAME 환경변수로 지정한 값입니다.
http://localhost:8501/v1/models/{모델이름}:predict
4.2 Python을 이용한 요청 예제
실제 애플리케이션 개발 시 가장 많이 사용하는 방식인 Python requests 모듈을 이용한 예제입니다. 데이터 전처리 후 서버로 전송하고 결과를 받는 전체 흐름을 확인해 보세요.
import json import requests import numpy as np # 1. 테스트 데이터 준비 # 모델의 입력 형태(Input Shape)에 맞춰 데이터를 준비해야 합니다. # 예: (1, 5) 형태의 2차원 리스트 (배치 차원 포함) test_data = np.random.rand(1, 5).tolist() # 2. 요청 페이로드(Payload) 구성 # TensorFlow Serving은 JSON 형식의 데이터를 받습니다. payload = { "signature_name": "serving_default", "instances": test_data } # 3. API 엔드포인트 URL 설정 url = 'http://localhost:8501/v1/models/my_model:predict' # 4. POST 요청 전송 headers = {"content-type": "application/json"} try: response = requests.post(url, data=json.dumps(payload), headers=headers) response.raise_for_status() # 상태 코드가 200이 아니면 예외 발생 # 5. 결과 파싱 및 확인 result = json.loads(response.text) predictions = result['predictions'] print("서버 응답 성공!") print("예측 결과:", predictions) except requests.exceptions.RequestException as e: print(f"API 호출 중 오류 발생: {e}") print(f"응답 내용: {response.text}")
여기서 가장 주의할 점은 instances 키를 사용하여 데이터를 감싸야 한다는 것입니다. TensorFlow Serving은 기본적으로 배치(Batch) 처리를 가정하기 때문에, 단 하나의 데이터를 보낼 때도 리스트 형태로 묶어서 보내야 합니다. 결과값 역시 리스트 형태로 반환됩니다.
5. 고급 기능: 모델 설정 파일(Model Config)과 배칭(Batching)
단일 모델을 테스트하는 단계를 넘어, 실제 서비스 운영을 위해서는 여러 모델을 동시에 띄우거나 성능을 최적화하는 설정이 필요합니다.
5.1 models.config 파일로 다중 모델 관리하기
여러 개의 모델을 하나의 서버에서 서빙하거나, 특정 버전 정책을 적용하고 싶다면 models.config 파일을 작성하여 컨테이너 실행 시 로드할 수 있습니다.
model_config_list { config { name: 'my_model' base_path: '/models/my_model' model_platform: 'tensorflow' model_version_policy: {all: {}} } config { name: 'another_model' base_path: '/models/another_model' model_platform: 'tensorflow' } }
이 설정 파일을 사용하면 model_version_policy를 통해 특정 버전만 서빙하거나, 모든 버전을 메모리에 올려두고 트래픽을 분산시키는 등의 세밀한 제어가 가능해집니다. Docker 실행 시에는 --model_config_file 옵션을 통해 이 파일을 지정하면 됩니다.
5.2 동적 배칭(Dynamic Batching)으로 처리량 극대화
실시간 서비스에서 처리량(Throughput)을 높이기 위해 가장 중요한 기능이 바로 배칭입니다. 동적 배칭은 개별적으로 들어오는 요청을 서버 내부에서 아주 짧은 시간(수 밀리초) 동안 대기시켰다가, 한 번에 묶어서 GPU로 연산하는 방식입니다.
이를 위해 batching_parameters.txt 파일을 생성하고 Docker 실행 시 --enable_batching 플래그를 추가하면 됩니다. 이 기능을 활성화하면 개별 요청의 응답 시간(Latency)은 아주 미세하게 늘어날 수 있지만, 동시간대 처리 가능한 요청의 수는 비약적으로 증가하여 서버 비용을 절감하고 급격한 트래픽 스파이크에 유연하게 대처할 수 있게 됩니다.
6. 문제 해결 및 팁 (Troubleshooting)
TensorFlow Serving 사용법을 익히다 보면 몇 가지 흔한 오류를 마주하게 됩니다. 미리 알아두면 좋은 해결 팁들입니다.
- "Servable not found for request" 오류:
- 가장 빈번한 오류입니다. 대부분 모델 경로가 잘못되었거나, 모델 버전 디렉토리(예:
1/)가 누락된 경우입니다. 컨테이너 내부 경로(mount옵션)가 올바른지, 폴더 구조가모델명/버전/saved_model.pb인지 다시 확인하세요.
- 가장 빈번한 오류입니다. 대부분 모델 경로가 잘못되었거나, 모델 버전 디렉토리(예:
- 입력 데이터 형상(Shape) 불일치:
- 모델이 기대하는 입력 차원과 전송하는 데이터의 차원이 다를 때 발생합니다. 특히 이미지 데이터의 경우
(Height, Width, Channel)인지(Batch, Height, Width, Channel)인지 확인하고,instances리스트를 통해 배치 차원을 맞추었는지 점검해야 합니다.
- 모델이 기대하는 입력 차원과 전송하는 데이터의 차원이 다를 때 발생합니다. 특히 이미지 데이터의 경우
- Docker 권한 문제:
- Linux 환경에서는
sudo를 사용하거나 현재 사용자를 docker 그룹에 추가해야 합니다. 또한, 마운트하려는 로컬 폴더의 읽기 권한이 있는지 확인하세요.
- Linux 환경에서는
결론: MLOps의 첫걸음, 서빙 자동화
지금까지 TensorFlow Serving 사용법에 대해 상세히 알아보았습니다. 모델을 SavedModel로 저장하고, Docker 컨테이너를 통해 배포하며, REST API로 통신하는 이 과정은 현대적인 AI 서비스 개발의 표준 프로세스입니다.
TensorFlow Serving을 도입함으로써 여러분은 모델 학습 코드와 서비스 코드를 완벽하게 분리할 수 있으며, 이는 서비스의 안정성과 확장성을 보장하는 핵심 열쇠가 됩니다. 이제 여러분의 로컬 컴퓨터에 잠들어 있는 AI 모델들을 깨워, 전 세계 어디서든 접근 가능한 API 서비스로 만들어 보시기 바랍니다. 더 나아가 Kubernetes 기반의 Kubeflow나 KServe와 연동한다면, 대규모 트래픽도 거뜬히 감당하는 엔터프라이즈급 AI 시스템을 구축할 수 있을 것입니다.