2026-07-22 로그
로컬 LLM 채팅 데모 구축 — 브라우저에서 Mac mini의 모델과 대화하기
벤치마크로 확인한 4개 모델을 브라우저에서 직접 써볼 수 있게 만들었다. 스트리밍, 동적 모델 전환, 그리고 삽질 두 개의 기록.
벤치마크로 숫자는 확인했으니, 다음은 당연한 수순 — 직접 써보는 것. 실험실 Mac mini 두 대에서 돌아가는 모델과 브라우저에서 대화하는 채팅 데모를 만들어 서비스 섹션에 공개했다.
구조
- 각 노드의
mlx_lm.server를 launchd 데몬으로 등록해 재부팅에도 살아남게 했다. 노드 2에는 GPU 메모리 한도를 부팅 시 자동으로 올려주는 데몬도 함께 넣었다 (gpt-oss-20b가 기본 한도로는 안 올라간다). - 프록시는 SSE 버퍼링을 끄고(스트리밍 유지) IP당 요청 속도 제한을 걸었다.
- 프론트는 프레임워크 없이 바닐라 JS. 응답은 marked + DOMPurify로 마크다운 렌더링한다 — gpt-oss가 표를 즐겨 그리기 때문에 효과가 크다.
모델 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)에 가벼운 반복 페널티를 얹으니 사라졌다.
사용성 마감
- 모델별로 성격에 맞는 예시 프롬프트를 입력창 placeholder에 표시 (4B는 요약, 14B는 비교 분석, gpt-oss는 표 정리 등)
- 응답마다 TTFT와 tok/s를 표시 — "이 응답은 Mac mini에서 생성됨"
- 대화 기록은 브라우저 메모리에만 존재. 서버에 저장하지 않는다.
공개 경로(에지 프록시 경유) 실측: Qwen3-4B 36.9 tok/s, gpt-oss-20b 33.9 tok/s — 로컬 벤치마크와 거의 같다. 16GB 미니 두 대가 진짜 서비스처럼 응답하는 걸 보는 재미가 있다.