2026-07-22 로그

로컬 LLM 채팅 데모 구축 — 브라우저에서 Mac mini의 모델과 대화하기

벤치마크로 확인한 4개 모델을 브라우저에서 직접 써볼 수 있게 만들었다. 스트리밍, 동적 모델 전환, 그리고 삽질 두 개의 기록.

벤치마크로 숫자는 확인했으니, 다음은 당연한 수순 — 직접 써보는 것. 실험실 Mac mini 두 대에서 돌아가는 모델과 브라우저에서 대화하는 채팅 데모를 만들어 서비스 섹션에 공개했다.

구조

브라우저 정적 채팅 페이지 · 바닐라 JS · 마크다운 렌더링 요청 JSON SSE 토큰 스트림 mac-ai.mintdev.uk 프록시 nginx · 요청 속도 제한 · SSE 무버퍼링 /api/fast/ → 노드 1 /api/oss/ → 노드 2 내부망 내부망 M4 노드 1 Mac mini M4 16GB · 상시 데몬 Qwen3-4B · ~40 tok/s Qwen3-14B · ~13 tok/s 요청의 model 필드로 동적 전환 M4 노드 2 Mac mini M4 16GB · 상시 데몬 Qwen3-8B · ~22 tok/s gpt-oss-20b · MoE ~35 tok/s GPU 한도 상향 데몬 포함

모델 4개, 서버는 2개

처음엔 노드당 모델 1개씩 고정으로 서빙했는데, mlx_lm.server가 요청의 model 필드로 동적 모델 전환을 지원한다는 걸 확인하고 벤치마크한 4개 모델을 전부 노출했다. 노드 1이 Qwen3-4B·14B, 노드 2가 Qwen3-8B·gpt-oss-20b를 담당한다.

트레이드오프도 명확하다. 전환 요청이 오면 노드가 이전 모델을 내리고 새 모델을 SSD에서 올리는데, 실측으로 14~37초가 걸린다. 그래서 전환이 필요한 첫 요청에는 "모델 변경 중…" 표시를 명시적으로 띄우고, 첫 토큰이 도착하면 지운다. 이후 요청부터는 제 속도가 난다.

삽질의 기록

1. gpt-oss가 답을 안 한다. 추론형 모델인 gpt-oss는 답변 전에 내부 분석(reasoning)을 길게 쓰는데, 기본 설정에서는 이 분석이 토큰 한도(2048)를 전부 소진해 최종 답변이 아예 안 나오는 경우가 있었다. 시스템 메시지로 Reasoning: low를 주면 분석이 짧아져 해결된다. 분석 텍스트 자체는 버리지 않고 "생각 과정" 접이식 박스로 보여준다.

2. 같은 말이 반복된다. Qwen3 계열로 테스트하다 보면 문장이 맴도는 반복 루프가 생겼다. 원인은 서버 기본값이 temperature 0(greedy 디코딩)이라는 것 — 소형 모델에서 greedy는 반복을 잘 만든다. Qwen 권장 샘플링(temperature 0.7, top-p 0.8)에 가벼운 반복 페널티를 얹으니 사라졌다.

사용성 마감

공개 경로(에지 프록시 경유) 실측: Qwen3-4B 36.9 tok/s, gpt-oss-20b 33.9 tok/s — 로컬 벤치마크와 거의 같다. 16GB 미니 두 대가 진짜 서비스처럼 응답하는 걸 보는 재미가 있다.