Gemma 서비스가 만들어지는
과정을 코드와 함께 이해한다
실제 fe/와 be/ 구조를 따라가며 왜 파일이 필요한지, 데이터가 어떻게 흐르는지, 실행 결과가 무엇을 증명하는지 기록하는 오프라인 학습 사이트다.
먼저 연결도를 보면 전체가 한눈에 들어온다
파일 14개와 그 사이를 요청이 옮겨 가는 25개의 경로를 한 장에 그린 지도다. 범례에서 GET /healthz·POST /api/v1/chat·실패 같은 흐름을 고르면 그 경로만 남고, 선마다 언제 그 이동이 일어나는지가 나타난다.
먼저 “서비스”와 “모델”을 분리해서 생각한다
LLM을 웹에서 사용한다고 해서 React가 Gemma의 가중치를 직접 읽는 것은 아니다. 사용자가 보는 채팅 화면, 요청을 통제하는 API 서버, 실제 행렬 연산을 수행하는 추론 서버는 서로 다른 책임을 가진 프로그램이다. 세 프로그램을 분리하면 한 부분을 교체해도 나머지 부분을 최대한 유지할 수 있다.
텍스트 입력, 메시지 목록, 로딩 표시처럼 사용자가 보고 조작하는 상태를 관리한다.
요청 형식을 검사하고, 동시 접속과 오류를 통제하며, 추론 결과를 브라우저에 중계한다.
토큰을 숫자로 바꾸고 Gemma 4를 실행해 다음 토큰의 확률을 계산한다.
왜 브라우저가 TurboFieldfare에 바로 요청하지 않나?
학습용 단일 사용자 환경에서는 직접 호출도 가능하다. 그러나 서비스가 되면 인증, 입력 길이 제한, 요청 대기열, 대화 저장, RAG와 MCP, 모델 교체 같은 정책이 필요해진다. 접근 권한의 최종 판단을 React에만 두면 사용자가 JavaScript 검사나 UI 제한을 우회해 API를 직접 호출할 수 있고, 모바일 앱 등 다른 client가 생길 때 같은 코드를 다시 만들어야 한다. FastAPI를 중간 경계로 두면 모든 client가 하나의 규칙을 공유할 수 있다.
Container는 웹 서비스를, macOS는 Metal 추론을 담당한다
아래는 목표 연결 구조다. 3단계에서 BE의 채팅 route까지 완성했고 실제 Gemma 생성을 POST /api/v1/chat으로 확인했다. 남은 것은 FE의 전송을 실제 요청으로 바꾸는 일, token streaming, 그리고 Compose 통합이다. FE의 전송 버튼은 현재도 사용자 문장을 화면에 추가하는 UI 동작이다.
화면과 사용자 입력
요청과 streaming 중계
macOS · Metal · Gemma 4
Frontend production container
local-moe-fe:study
/healthz
/
26,017,241 bytes
Backend inference adapter와 채팅 route
설정·오류·입력 상한·실제 TCP·수명
/readyz
Host와 Docker
host.docker.internal
생성·상태 확인 연결을 분리하고 오류 분류, 실제 시간 초과·취소 테스트, 서비스 문맥 용어 설명을 보완했다. 초기 Gemma 생성 기록과 이번 회귀 검증은 구분해 기록했다.
완성 후 질문 한 문장이 답변 토큰으로 돌아오는 과정
React는 입력 문자열과 이전 대화를 하나의 요청 객체로 만들고 화면에는 사용자 메시지를 즉시 추가한다.
빈 질문과 입력 형식·접근 권한 같은 서비스 정책을 검사한 뒤 TurboFieldfare 형식으로 변환한다. 정확한 context 길이는 tokenizer·chat template에 좌우되므로 현재는 Swift 서버가 검증하고 Adapter가 문맥 초과 오류를 전달한다. FastAPI에 동일 tokenizer가 구현됐다는 뜻은 아니다.
prompt를 token ID로 변환하고 prefill을 수행한 뒤 decode loop에서 다음 token을 하나씩 생성한다.
완성된 답변을 한 번에 기다리지 않고 token 조각을 FastAPI와 React로 전달한다. React는 같은 assistant message의 내용을 계속 이어 붙인다.
이 문서를 읽는 순서
- 요청 흐름 연결도에서 파일 사이를 요청이 어떻게 옮겨 다니는지 전체 지도를 먼저 본다.
- Frontend 구조에서 React source가 브라우저 화면이 되는 큰 흐름을 잡는다.
- Dockerfile에서 개발 도구와 production 실행 환경을 왜 분리하는지 확인한다.
- Nginx에서 URL 하나가 어떤 파일과 HTTP 응답으로 바뀌는지 추적한다.
- Backend 2단계에서 범용 inference 계약, TurboFieldfare Adapter, readiness를 이해한다.
- base.py와 turbofieldfare.py를 비교해 내부 데이터와 wire JSON의 경계를 추적한다.
- 다음 단계에서는 이 Adapter를 사용하는 저장 없는 단일 채팅 API를 만든다. Streaming은 그 다음 단계에서 별도로 검증한다.
각 문서는 실제 source file 하나를 기준으로 작성한다. 따라서 개념 설명을 읽은 직후 실제 코드의 어느 줄이 그 개념을 구현하는지 확인할 수 있다.