154 lines
7.7 KiB
Markdown
154 lines
7.7 KiB
Markdown
# 370. 임베딩 서비스 분리 아키텍처
|
|
|
|
## 개요
|
|
|
|
각 로빙이 독립적으로 '언어 의미 이해 엔진(ONNX 임베딩 모델)'을 탑재하던 구조에서, 중앙의 '언어 의미 번역 센터(임베딩 서비스)'를 함께 사용하는 구조로 전환하여 메모리 사용량을 극적으로 감소시키고 관리 효율성을 확보한 과정을 설명합니다.
|
|
|
|
> "모든 로빙이 하나의 전문 번역가를 공유하면, 각자의 부담은 가벼워지고 소통의 질은 유지된다."
|
|
|
|
## 왜 임베딩 서비스를 분리했나?
|
|
|
|
### 임베딩이란?
|
|
- **정의**: **임베딩은 "단어/문장의 의미"를 컴퓨터가 이해할 수 있는 숫자 배열(벡터)로 번역하는 기술**입니다.
|
|
- **작동 방식**: "안녕하세요"라는 텍스트를 받으면, [0.1, 0.3, -0.5, ...] 와 같이 수백 개의 숫자 배열로 변환합니다.
|
|
- **핵심 역할**: 의미가 비슷한 텍스트는 이 숫자 배열의 패턴도 유사해집니다. 이를 통해 로빙은 단어의 표면적 의미를 넘어, 문맥과 뉘앙스를 파악하는 '의미 기반 검색'을 할 수 있습니다. 로빙의 기억과 이해의 핵심 기술입니다.
|
|
|
|
### 분리 전 문제점: "모두가 각자 사전을 들고 다니는 비효율"
|
|
1. **메모리 낭비**: 모든 로빙이 동일한 '언어 의미 사전(임베딩 모델, 약 450MB)'을 각자 메모리에 올려두고 있었습니다. 로빙 10개가 동시에 작동하면, 똑같은 사전 10개를 펴놓는 것과 같아 약 4.5GB의 메모리가 낭비되었습니다.
|
|
2. **느린 시작 시간**: 각 로빙이 부팅될 때마다 이 무거운 사전을 펼치는 데(모델 로드) 5~10초의 시간이 걸렸습니다.
|
|
3. **어려운 업데이트**: 사전의 새 버전이 나올 때마다, 모든 로빙의 사전을 일일이 교체하고 재시작해야 하는 번거로움이 있었습니다.
|
|
|
|
### 분리 후 장점: "마을 중앙 도서관을 함께 이용하는 효율"
|
|
1. **메모리 절약**: 이제 마을 중앙 도서관(중앙 임베딩 서비스)에 가장 좋은 사전 한 권만 비치하고, 모든 로빙이 필요할 때마다 와서 찾아봅니다. 메모리 낭비가 사라졌습니다.
|
|
2. **빠른 시작**: 로빙은 더 이상 무거운 사전을 들고 다닐 필요 없이, 가볍게 시작하여 도서관에 질문만 하면 됩니다.
|
|
3. **쉬운 관리**: 새 사전이 나오면, 도서관의 사전 한 권만 교체하면 모든 로빙이 즉시 최신 정보를 이용할 수 있습니다.
|
|
|
|
## 해결책: 중앙 임베딩 서비스 (skill-embedding)
|
|
|
|
로빙의 여러 기능 중, '언어 의미 이해'라는 전문적인 역할을 별도의 **마이크로서비스(Microservice)**, 즉 '기능별로 나뉜 전문 서비스'로 분리했습니다.
|
|
|
|
### 아키텍처: "각자의 집과 중앙 도서관"
|
|
|
|
```
|
|
이전: "모든 집에 두꺼운 백과사전 비치"
|
|
┌─────────────┐
|
|
│ rb10508의 집 │
|
|
│ 백과사전 (988MB) │
|
|
└─────────────┘
|
|
|
|
┌─────────────┐
|
|
│ rb8001의 집 │
|
|
│ 백과사전 (416MB) │
|
|
└─────────────┘
|
|
|
|
이후: "가벼운 집 + 중앙 도서관"
|
|
┌─────────────┐
|
|
│ rb10508의 집 │
|
|
│ (118MB) │──┐
|
|
└─────────────┘ │
|
|
│ (API 통신: "이 단어 뜻이 뭐야?")
|
|
▼
|
|
┌─────────────┐ ┌─────────────────┐
|
|
│ rb8001의 집 │ │ 중앙 도서관 (skill-embedding) │
|
|
│ (200MB) │──│ (874.4MB) │
|
|
└─────────────┘ │ - 최고의 백과사전 1권 │
|
|
│ - 24시간 운영 (Port 8515) │
|
|
▼ └─────────────────┘
|
|
┌─────────────┐
|
|
│ rb10408의 집 │
|
|
│ (30MB) │──┘
|
|
└─────────────┘
|
|
```
|
|
|
|
### 기술 스택 선택 이유
|
|
|
|
- **FastAPI + Uvicorn**:
|
|
- **왜?** Python 기반의 초고속 도로. 여러 로빙이 동시에 질문해도 막힘없이 처리(비동기)할 수 있습니다.
|
|
|
|
- **ONNX Runtime**:
|
|
- **왜?** 같은 사전이라도 더 가볍고 빠르게 읽을 수 있게 해주는 기술. 기존 방식(PyTorch)보다 3배 빠르고 메모리는 절반만 사용합니다.
|
|
|
|
- **Ko-SRoBERTa(multitask) SentenceTransformer** → ONNX 변환 (768차원):
|
|
- **왜?** 한국어 대화·명령 코퍼스에 특화돼 의도 분류·콜드메일 필터 정확도가 MiniLM 대비 20pt 이상 향상되었습니다.
|
|
|
|
### API 설계: "간단한 질문과 명확한 답변"
|
|
|
|
API는 서비스끼리 대화하는 약속(규칙)입니다. 로빙과 임베딩 서비스는 다음과 같이 간단한 대화를 나눕니다.
|
|
|
|
```python
|
|
# 로빙의 질문 (POST /embed)
|
|
{
|
|
"texts": ["안녕하세요", "오늘 날씨가 좋네요"]
|
|
}
|
|
|
|
# 임베딩 서비스의 답변 (숫자 배열)
|
|
{
|
|
"embeddings": [
|
|
[0.1, 0.2, ...], # "안녕하세요"의 의미
|
|
[0.3, 0.4, ...] # "오늘 날씨가 좋네요"의 의미
|
|
],
|
|
"model": "jhgan/ko-sroberta-multitask",
|
|
"dimensions": 768
|
|
}
|
|
```
|
|
|
|
## 구현 가이드
|
|
|
|
### 1. 로빙 측 변경사항: "직접 찾지 않고, 물어보기"
|
|
|
|
```python
|
|
# 이전: 로빙이 직접 무거운 사전을 뒤적임 (메모리 낭비)
|
|
from onnx_embedder import ONNXEmbedder
|
|
embedder = ONNXEmbedder("/models/onnx/...") # 449MB 모델 로드
|
|
|
|
# 변경: 가볍게 도서관(임베딩 서비스)에 전화해서 물어봄
|
|
class HTTPEmbeddingFunction(EmbeddingFunction):
|
|
def __init__(self):
|
|
# 도서관의 전화번호(URL)는 환경에 따라 설정
|
|
self.url = f"{os.getenv('SKILL_EMBEDDING_URL', 'http://localhost:8515')}/embed"
|
|
|
|
def __call__(self, input: List[str]) -> List[List[float]]:
|
|
# 물어볼 단어들을 서비스로 보내고, 의미(벡터)를 받아옴
|
|
response = requests.post(self.url, json={"texts": input}, timeout=30)
|
|
return response.json()["embeddings"]
|
|
```
|
|
|
|
### 2. ChromaDB 통합: "로빙의 기억 노트"
|
|
|
|
```python
|
|
# memory.py - 로빙의 기억을 저장하는 코드
|
|
# 각 로빙은 자신만의 기억 노트(Collection)를 가짐
|
|
self.episodic = self.client.get_or_create_collection(
|
|
name=f"{self.robeing_id}_episodic",
|
|
# 단어의 의미를 물어볼 때는 중앙 도서관(HTTPEmbeddingFunction)을 이용
|
|
embedding_function=HTTPEmbeddingFunction()
|
|
)
|
|
# 결과: 기억은 각자 독립적으로, 의미 분석은 중앙에서 공유
|
|
```
|
|
|
|
## 성능 분석
|
|
|
|
### 메모리 절감 (실측 데이터)
|
|
```
|
|
로빙 이름 | 이전 메모리 | 이후 메모리 | 절감량
|
|
------------|------------|------------|--------
|
|
rb10508 | 988MB | 118MB | -870MB (88%↓)
|
|
rb8001 | 416MB | 200MB | -216MB (52%↓)
|
|
rb10408 | 55MB | 30MB | -25MB (45%↓)
|
|
|
|
# 예상 절감 효과
|
|
- 로빙 10개 운영 시: 8.7GB 메모리 절약
|
|
- 로빙 100개 운영 시: 87GB 메모리 절약 (서버 1대 가격)
|
|
```
|
|
|
|
### 속도 영향
|
|
- **내부 통신 시간**: MiniLM 대비 약 +30ms 증가(총 35~40ms)하지만, 의도·콜드메일 정확도 향상 효과가 더 큽니다.
|
|
|
|
## 교훈
|
|
|
|
- **전문가의 원리**: 모두가 전문가가 될 필요는 없습니다. '언어 의미 이해'라는 전문적인 작업은 전문가(서비스)에게 맡기고, 나머지는 자신의 역할에 집중하는 것이 효율적입니다.
|
|
- **공유와 독립의 균형**: 핵심 엔진(임베딩)은 공유하여 효율을 높이되, 각자의 기억과 개성은 철저히 독립적으로 유지해야 합니다.
|
|
|
|
---
|
|
|
|
*"임베딩은 공유하되, 기억은 분리하라"* |