Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/embedding-server/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# torch를 NVIDIA 독점 패키지(nvidia-*)가 딸려오는 GPU용이 아닌 CPU 전용 빌드로 받는다.
--extra-index-url https://download.pytorch.org/whl/cpu
fastapi==0.115.0
uvicorn==0.30.6
FlagEmbedding==1.2.11
Expand Down
91 changes: 91 additions & 0 deletions docs/design/kangcheolung-#450-embedding-torch-cpu-only.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# #450 임베딩 서버 torch를 CPU 전용으로 교체해 NVIDIA 독점 패키지 제거

closes #450

---

## 배경

대회의 오픈소스 라이선스 평가 기준은 OSI 승인 라이선스이고, 평가는 각 평가 시점의 저장소 상태로 이루어진다. (#399 참고)

`backend/embedding-server/requirements.txt`의 `torch==2.4.1`을 linux/amd64에서 설치하면 NVIDIA CUDA 런타임 패키지 12개가 의존성으로 함께 설치된다.

```text
nvidia-cublas-cu12 12.1.3.1 nvidia-cufft-cu12 11.0.2.54
nvidia-cuda-cupti-cu12 12.1.105 nvidia-curand-cu12 10.3.2.106
nvidia-cuda-nvrtc-cu12 12.1.105 nvidia-cusolver-cu12 11.4.5.107
nvidia-cuda-runtime-cu12 12.1.105 nvidia-cusparse-cu12 12.1.0.106
nvidia-cudnn-cu12 9.1.0.70 nvidia-nccl-cu12 2.20.5
nvidia-nvjitlink-cu12 12.9.86 nvidia-nvtx-cu12 12.1.105
```

- 이 패키지들의 라이선스는 NVIDIA 독점(NVIDIA Proprietary Software)이며 OSI 승인이 아니다. (PyPI 메타데이터 기준)
- 개발 PC(Apple Silicon, linux/arm64) 이미지에는 설치되지 않아 드러나지 않았다. 변경 전 이미지에서 `pip list | grep -ci nvidia`는 arm64 `0`, amd64 `12`였다.
- 임베딩 서버는 `docker-compose.yml`에서 GPU 없이 CPU만 사용하므로(`cpus`, `mem_limit`) 이 패키지는 실행에 쓰이지 않는다. (`torch.cuda.is_available()` → `False`)

---

## 결정

**torch 버전은 유지하고, PyTorch CPU 전용 저장소를 추가 인덱스로 지정한다.**

`requirements.txt` 맨 위에 한 줄을 추가한다.

```text
--extra-index-url https://download.pytorch.org/whl/cpu
```

- `torch==2.4.1`이 CPU 전용 빌드 `2.4.1+cpu`로 선택된다. 이 빌드는 `nvidia-*`와 `triton`을 요구하지 않는다.
- `Dockerfile`이 `pip install -r requirements.txt`를 쓰고, `requirements-test.txt`도 `-r requirements.txt`로 이 파일을 참조하므로 **이미지와 로컬 테스트가 같은 설정을 쓴다.**
- 서버 코드(`main.py`)와 API 계약은 변경하지 않는다.

### 선택하지 않은 방법

- **`Dockerfile`의 `pip install`에만 인덱스를 지정**: 로컬 테스트 환경(`requirements-test.txt`)이 GPU용 torch를 받아 이미지와 어긋난다.
- **`--index-url`로 기본 인덱스를 교체**: PyPI의 나머지 패키지를 받지 못한다. 추가 인덱스로 충분하다.
- **torch 버전 변경**: 라이선스 문제가 버전이 아니라 빌드 종류에 있어 불필요하다.

---

## API 명세

변경 없음. `/embed`, `/embed/batch`, `/health*`, `/metrics`의 요청·응답·에러 계약은 그대로다.

---

## 변경 범위

| 파일 | 변경 |
|---|---|
| `backend/embedding-server/requirements.txt` | CPU 전용 torch 저장소 `--extra-index-url` 추가 |

---

## 검증

| 항목 | 결과 |
|---|---|
| linux/amd64 `pip install --dry-run` | 76개 → 63개. `nvidia-*` 12개와 `triton` 제거, `torch 2.4.1+cpu` 선택 |
| linux/amd64 이미지 빌드 | `pip list`의 `nvidia-*` **0개**(변경 전 12개), `torch 2.4.1+cpu`, `import torch` 성공, `cuda_available False` |
| linux/amd64 이미지 크기 | 디스크 사용량(압축 해제 후, `docker images`) 9.29GB → **2.29GB**, 압축 기준(`docker image inspect`) 3.16GB → **0.50GB** |
| linux/amd64 기동 (에뮬레이션, 변경 전·후 각각) | 모델 로딩 후 `/health/ready` **200**(약 10초), `uname -m`=`x86_64`. 변경 전 `torch 2.4.1+cu121`·`nvidia-*` 12개, 변경 후 `torch 2.4.1+cpu`·`nvidia-*` 0개 |
| 임베딩 값 비교 (linux/amd64, 고정 문장 6개, `/embed/batch`, 1024차원) | 변경 전·후 **모든 값이 완전히 일치** (`cos=1.0`, 최대 절대 오차 `0`) |
| 임베딩 값 비교 (linux/arm64, 같은 문장) | 변경 전·후 **모든 값이 완전히 일치** (`cos=1.0`, 최대 절대 오차 `0`) |
| `pytest test_main.py` (변경 후 이미지, arm64) | 22 passed |
| 기준 이미지 | 변경 전 이미지는 `origin/develop`의 `requirements.txt`로 같은 방식으로 빌드해 비교 |

참고: 같은 변경 전 이미지에서 arm64와 amd64의 임베딩을 비교하면 `cos=0.9999964`, 최대 절대 오차 `3.0e-4`로 아키텍처 사이에는 미세한 차이가 있다. 이 차이는 이번 변경과 무관한 CPU 아키텍처 차이이므로, 비교 기준은 **같은 아키텍처에서의 변경 전·후**다.

### 검증하지 못한 범위

- amd64 확인은 **Apple Silicon Mac의 Docker linux/amd64 에뮬레이션**에서 수행했다. 실제 x86 하드웨어에서 계산한 값은 아니다. 변경 전·후 값이 비트 단위로 같았으므로 CPU 전용 빌드가 결과를 바꾸지 않는다고 판단하지만, 실제 x86 서버에서의 재확인은 배포 때 필요하다.
- 배포 환경(GCP VM 등)에서의 실제 기동은 이 이슈에서 확인하지 않았다. 배포 시 `/health/ready`와 임베딩 값으로 확인이 필요하다.

---

## 이 이슈의 범위 밖

- `minio/minio`(태그 미고정, AGPL-3.0), `grafana/grafana:13.2.3`(AGPL-3.0): OSI 승인 라이선스이나 별도 컨테이너 실행의 충돌 여부는 OpenUP 멘토링(~10/26)에서 확인한다.
- `docs/images/architecture.png`의 Redis 로고, README 라이선스 표 보강
- `FlagEmbedding==1.2.11`은 PyPI 라이선스 메타데이터가 비어 있다.
- 라이선스는 POM·PyPI·npm 메타데이터 기준이며 소스 스캔 결과와 다를 수 있다.
Loading