배포

환경 이식성 확보: 코드 분기 대신 설정 주도 배포 구조로의 전환

2026. 06. 24

로브로스 온디바이스 로봇을 개발하며 서버와 엣지 디바이스를 모두 지원해야 했습니다. if 문 분기가 코드 전반으로 번지는 것을 막고, YAML 프로파일 교체만으로 4개 구성을 전환하게 만든 과정입니다.

프로젝트 배경

소속
로브로스 · Physical AI 사업부 AI개발팀 (인턴)
프로젝트
휴머노이드 로봇용 실시간 멀티모달 HRI 시스템
기간
2025.06 ~ 2025.09 (이후 개인 확장으로 웹 데모 운영)
담당
단독 개발 · 설계-구현-실측-배포 전 과정

휴머노이드 안내로봇에 탑재할 실시간 Vision-Language 대화 모듈을 단독 개발한 프로젝트입니다. 로봇이 네트워크 없이 단독 동작해야 한다는 요구사항 때문에 Jetson Orin 온디바이스에서 모든 추론을 처리해야 했고, 가용 VRAM은 8GB였습니다. 사람의 지시가 언어와 시각 맥락이 결합된 형태로 들어오기 때문에 VLM과 대화 LLM을 함께 올려야 했다는 점이 핵심 제약이었습니다.

배포설정관리엣지컴퓨팅이식성아키텍처

1. 배경: 환경이 바뀔 때마다 코드가 갈라지는 구조

로브로스에서 온디바이스 대화 시스템을 만들면서 요구사항이 하나 더 붙었습니다. 환경이 하나가 아니었습니다.

개발할 때는 RTX 서버에서 돌립니다. 빠르고 편합니다. 실제 로봇에 올릴 때는 Jetson입니다. 느리고 메모리가 적습니다. 시연 상황에 따라 로봇이 서버를 호출하기도 하고, 네트워크 없이 단독으로 돌아야 하기도 합니다.

정리하면 네 가지 구성이 필요했습니다.

구성실행 위치네트워크
CASE 1RTX 서버 (GPU FastAPI)필수
CASE 2RTX 서버 (llama.cpp)필수
CASE 3Jetson → 원격 서버 호출필수
CASE 4Jetson 단독오프라인

2. 문제 정의: 환경 분기의 확산

처음에는 조건문으로 처리했습니다. 환경이 둘일 때 이는 옳은 선택입니다. 설정 파일을 만들고 로더를 붙이는 비용보다 if 한 줄이 싸고, 분기가 코드 옆에 있으니 읽는 사람이 다른 파일을 열 필요도 없습니다.

pythonif device == "jetson":
    model_path = "...q4_k_m.gguf"
    n_gpu_layers = 20
else:
    model_path = "...fp16.gguf"
    n_gpu_layers = -1

이 선택은 환경 수에 따라 뒤집힙니다. 분기가 하나씩 늘었습니다. 모델 경로, GPU 레이어 수, 배치 크기, 스레드 수, 포트 번호, 엔드포인트 주소... 환경마다 다른 값이 생각보다 많았습니다.

조건문의 비용은 분기 수에 비례해 늘어나는데, 설정 파일의 비용은 처음 한 번뿐입니다. 어느 지점에서 역전됩니다. 초기 구조가 틀린 것이 아니라 그 지점을 지났는데 그대로 두었던 것이 문제였습니다.

문제는 세 가지 방식으로 나타났습니다.

첫째, 분기가 여기저기 흩어졌습니다. 모델 로딩 코드에도, 라우팅 코드에도, 초기화 코드에도 조건문이 들어갔습니다. 새 환경을 추가하려면 어디를 고쳐야 하는지 찾아다녀야 했습니다.

둘째, 조합이 폭발했습니다. "Jetson인가"와 "네트워크를 쓰는가"는 별개의 축인데, 조건문으로 쓰다 보니 if jetson and remote 같은 중첩이 생겼습니다. 축이 늘어날 때마다 경우의 수가 곱해졌습니다.

셋째, 테스트한 조합만 동작했습니다. 서버에서 개발하고 Jetson에서 처음 돌리면 반드시 어딘가에서 오류가 발생했습니다. 어떤 분기를 안 탔는지 알 수 없었습니다.

3. 기술적 난점

돌아보면 환경 차이를 로직으로 다루고 있었던 것이 문제였습니다.

"이 환경이면 이렇게 합니다"는 판단이 코드 안에 박혀 있으니, 환경 정보와 실행 로직이 뒤엉켰습니다. 다만 실제로 환경마다 달라지는 건 동작 방식이 아니라 값이었습니다. 모델을 로드하는 절차는 같고 경로와 파라미터만 다릅니다. 라우팅하는 방식은 같고 대상 주소만 다릅니다.

같은 로직에 다른 값을 넣는 문제를, 다른 로직으로 취급하고 있었습니다.

4. 대안 검토

1안. 환경별로 브랜치나 빌드를 분리합니다

  • 장점: 각 환경에 최적화된 코드를 둘 수 있습니다.
  • 단점: 동기화 지옥입니다. 공통 로직을 고치면 모든 브랜치에 반영해야 합니다. 한 곳을 빠뜨리면 환경마다 다르게 동작합니다.

2안. 환경 변수로 값을 주입합니다

  • 장점: 간단하고 표준적입니다.
  • 단점: 값이 수십 개가 되면 관리가 어렵습니다. 어떤 조합이 유효한지 문서 없이는 알 수 없고, 계층 구조를 표현하기 불편합니다.

3안. 설정 파일(프로파일)로 환경을 통째로 기술합니다

환경별 설정을 YAML 파일 하나로 만들고, 실행 시 어떤 프로파일을 쓸지만 지정합니다.

  • 장점: 환경 하나가 파일 하나로 완결됩니다. 무엇이 다른지 눈으로 비교됩니다.
  • 단점: 설정 스키마를 잘 설계해야 하고, 코드가 설정을 읽는 계층이 필요합니다.

5. 설계 결정 및 근거

3안, 프로파일 방식을 골랐습니다.

기준은 이거였습니다. 환경 차이는 데이터이지 로직이 아니다.

이 관점으로 보니 정리가 됐습니다. 코드는 "설정에 적힌 대로 실행한다" 하나만 알면 됩니다. 설정에 무엇이 적혀 있는지는 코드가 알 필요가 없습니다. 이 구조에서는 환경 분기가 제거됩니다.

설정 로딩 지점의 단일화

가장 먼저 한 일은 설정을 읽는 계층을 분리하는 것이었습니다. 코드 곳곳에서 환경을 확인하던 것을 그만두고, 시작 시점에 프로파일을 한 번 로드해 나머지 코드에는 이미 결정된 값만 전달했습니다.

[ 이전 ]                        [ 이후 ]
코드 곳곳에서 환경 확인          시작 시 프로파일 1회 로드
if jetson: ...                      ↓
if remote: ...                  코드는 값만 받아 사용
(분기가 흩어짐)                 (분기 없음)

이렇게 하니 새 환경을 추가하는 일이 YAML 파일 하나 만드는 일이 됐습니다. 코드는 건드리지 않습니다.

설정과 로직의 분리 기준

전부 설정으로 빼면 설정 파일이 프로그램이 됩니다. 이에 따라 기준을 세웠습니다.

환경에 따라 값이 달라지는 것만 설정으로 뺐습니다. 모델 경로, 양자화 수준, GPU 레이어 배분, 스레드 수, 배치 크기, 포트, 엔드포인트 주소 같은 것들입니다.

동작의 순서나 조건은 코드에 남겼습니다. "RAG를 먼저 보고 룰을 보고 LLM으로 간다"는 흐름은 어느 환경에서나 같습니다. 이는 설정이 아니라 로직입니다.

이 구분을 지키니 설정 파일이 읽을 만한 크기로 유지됐습니다.

엣지 환경 튜닝의 파라미터화

Jetson에서는 서버와 다른 문제들이 있었습니다. CUDA와 프레임워크 간 버전 호환이 맞지 않았고, 메모리가 부족하면 스와핑이 일어나 응답이 크게 느려졌습니다.

이런 대응도 대부분 파라미터 값으로 흡수됐습니다. 멀티스레드 수를 조정하고, 배치 크기를 제한하고, 스와핑을 차단하는 옵션을 켜는 식입니다. 전부 프로파일에 적히는 값이라 코드 분기가 필요 없었습니다.

콜드 스타트 지연 개선

배포 구성을 정리하다 보니 다른 문보였습니다. 첫 응답이 유독 느렸습니다. 모델을 메모리에 올리는 시간이 첫 요청에 그대로 얹혔습니다.

두 가지로 대응했습니다. 지연 로딩(Lazy Loading) 으로 실제 필요한 시점에 올리되, 세션 단위 인메모리 캐싱으로 한 번 올린 것은 재사용하게 했습니다. 결과적으로 최초 응답 지연이 53% 단축됐고, 연속 질의에서 VRAM이 새는 현상도 잡혔습니다.

6. 실험 결과

항목결과
지원 배포 구성4가지 (서버 2 · Jetson 원격 · Jetson 단독)
환경 전환 방법YAML 프로파일 교체 (코드 수정 0)
콜드 스타트53% 단축
아키텍처4개 구성에서 동일 구조 유지

가장 의미 있었던 건 마지막 줄입니다. 네 가지 구성이 서로 다른 시스템이 아니라 같은 시스템의 다른 설정이 됐습니다. 성능이 필요하면 서버 구성을, 오프라인과 프라이버시가 필요하면 Jetson 단독 구성을 고르면 됩니다. 요구사항에 따라 배포 선택지를 제공할 수 있게 된 것입니다.

7. 결론 및 시사점

환경 차이를 로직으로 다루면 조합이 폭발합니다. 조건문 하나를 추가할 때는 별일 아닌 것 같았는데, 축이 두 개가 되는 순간 관리가 어려워졌습니다. 값으로 분리할 수 있는 것을 로직에 두지 않는다는 원칙 하나로 대부분이 정리됐습니다.

설정으로 뺄 것과 코드에 남길 것을 구분하는 게 핵심입니다. 전부 설정화하면 YAML이 읽을 수 없는 프로그램이 됩니다. "환경에 따라 달라지는 값"만 빼고 "동작의 흐름"은 코드에 남긴다는 기준이 균형점이었습니다.

이식성은 나중에 붙이기 어렵습니다. 만약 서버 전용으로 다 만들어놓고 나중에 Jetson 대응을 했다면 훨씬 힘들었을 것입니다. 처음부터 "환경이 바뀔 수 있습니다"고 가정하고 값을 밖으로 빼두니, 새 환경 추가가 파일 하나 만드는 일로 끝났습니다.