16GB Mac mini에서 9B급 MLX 4bit 모델 두 개를 oMLX로 같은 프롬프트에 돌려 본 기록

M4 Mac mini 16GB에서 oMLX로 Qwen3.8-9B와 Ornith-1.5-9B(둘 다 MLX 4bit)를 요약 프롬프트와 코드 수정 프롬프트 각 3회씩 돌렸습니다. 첫 토큰 시간 0.9~1.5초, 생성 속도 13.8~18.5 tok/s, 모델 메모리 약 4.9GiB였고, 두 모델의 속도 차이는 오차 안이었습니다.

결론 먼저

두 모델은 구조와 양자화가 같아서 속도와 메모리가 사실상 같았습니다. 16GB Mac mini에서 하나는 약 4.9GiB, 둘을 동시에 올려도 약 9.9GiB였고 생성은 초당 14~18토큰이었습니다. 차이는 말투와 설명 길이에서만 보였습니다.

실험 환경
Apple M4 Mac mini 16GB, macOS 27.0.1, oMLX 0.7.0. 가중치는 외장 볼륨에 둔 MLX 4bit(group size 64) 두 개, 각 4.7GB입니다.
누구에게 유용한가
16GB Mac에서 9B급 모델을 oMLX로 돌려 보려는데 모델을 고르기 전에 실제 속도와 메모리 숫자를 보고 싶은 사람
확인한 자료와 한계
확인한 자료
  • 직접 측정: oMLX 0.7.0 /v1/chat/completions 응답의 usage 지표와 /health의 engine_pool 값 (2026-10-09)
  • keXjos/Qwen3.8-9B-mlx-4Bit 모델 카드 (base_model: empero-ai/Qwen3.8-9B, mlx-lm 0.31.2 변환)
  • ornith-ai/Ornith-1.5-9B-MLX-4bit 모델 카드
한계

프롬프트 2종, 각 3회, 입력 약 170~260토큰, 출력 최대 450토큰입니다. 긴 컨텍스트, 사고 모드를 켠 상태의 Ornith, 지속 부하는 재지 않았습니다. 파일 캐시를 비우지 못해 콜드 로드는 Ornith 한 번만 측정했습니다.

이 글에서 다루는 핵심 3가지

16GB Mac mini에서 9B급 4bit 모델을 oMLX로 돌리면 모델 하나가 약 4.9GiB를 쓰고 생성 속도는 초당 14~18토큰 사이에서 움직였습니다. Qwen3.8-9B와 Ornith-1.5-9B를 같은 프롬프트로 돌려 봤는데 두 모델의 속도 차이는 매번의 흔들림보다 작았습니다. 아래는 2026-10-09에 잰 값이고 프롬프트는 두 종류뿐이라 “이 두 프롬프트에서의 기록”으로 읽어 주세요.

측정한 환경

항목 값
기기 Apple M4 Mac mini, 메모리 16GB
OS / 런타임 macOS 27.0.1, oMLX 0.7.0 (OpenAI 호환 서버, 127.0.0.1:8000)
모델 A keXjos/Qwen3.8-9B-mlx-4Bit, 4bit affine, group size 64, 디스크 4.7GB
모델 B ornith-ai/Ornith-1.5-9B-MLX-4bit, 4bit affine, group size 64, 디스크 4.7GB
공통 두 모델 모두 config의 구조가 Qwen3_5ForConditionalGeneration, 32층, 최대 컨텍스트 262144
생성 설정 temperature 0.2, max_tokens 450, 사고 모드 끔

모델 카드에 적힌 출처는 서로 다릅니다. Qwen3.8-9B는 empero-ai/Qwen3.8-9B를 mlx-lm 0.31.2로 변환한 것이고 Ornith-1.5-9B는 Ornith 팀이 직접 낸 9B dense 모델입니다. 두 모델의 벤치마크 점수는 이 글에서 다루지 않았고 확인하지도 않았습니다.

프롬프트와 지표를 잡은 방식

프롬프트는 두 가지였습니다. 하나는 로컬 LLM 메모리에 대한 한국어 문단(입력 약 260토큰)을 세 문장 이내로 요약하는 것이고 다른 하나는 이동 평균 함수의 off-by-one 버그를 한 줄로 설명하고 고치는 것(입력 약 170토큰)입니다. 모델마다 두 프롬프트를 3번씩 돌렸습니다.

지표는 oMLX 응답에 들어 있는 usage 값을 그대로 썼습니다. 첫 토큰 시간(time_to_first_token), 초당 생성 토큰(generation_tokens_per_second), 모델 로드 시간(model_load_duration)입니다. 메모리는 요청 전후의 /health에서 engine_pool.current_model_memory를 읽었습니다. 같은 문장이 되풀이되면 서버의 프롬프트 캐시가 먹혀서 첫 토큰 시간이 달라지기 때문에, 본 측정에서는 요청마다 다른 접두 문자열을 붙였습니다.

숫자로 본 결과

항목 Qwen3.8-9B Ornith-1.5-9B
첫 토큰 시간, 요약 프롬프트 1.43~1.52초 (평균 1.46) 1.32~1.37초 (평균 1.35, 2회)
첫 토큰 시간, 코드 프롬프트 1.00~1.07초 (평균 1.02) 0.91~0.93초 (평균 0.92)
생성 속도 범위, 6회 14.4~16.3 tok/s 13.8~18.5 tok/s
생성 속도 평균, 요약 15.6 tok/s 17.6 tok/s
생성 속도 평균, 코드 16.1 tok/s 15.8 tok/s
평균 출력 길이, 요약 / 코드 112 / 109 토큰 117 / 167 토큰
모델 하나의 메모리 4.93GiB (5.3GB) 4.92GiB
로드 시간 3.6초 6.2초 (아래 참고)

Ornith의 요약 첫 토큰 시간은 그 세션에서 처음 불러온 1회를 뺀 2회 평균입니다. 그 첫 요청은 모델 로드까지 포함해 81.4초가 걸렸습니다(로드 51.5초, 첫 토큰 22.6초). 같은 요청에서 생성 속도는 17.3 tok/s로 다른 회차와 비슷했습니다.

속도 차이를 해석하면 두 모델이 같은 구조에 같은 4bit이고 파일 크기도 같으니 속도가 비슷한 건 자연스럽다고 봅니다. 이건 제 추정이고 이 측정이 증명한 것은 아닙니다. 평균은 요약에서 Ornith가 초당 2토큰 정도 빠르고 코드에서는 반대인데, 같은 모델 안에서도 13.8에서 18.5까지 흔들렸습니다. 이 정도 표본으로는 어느 쪽이 빠르다고 말할 수 없습니다.

둘을 동시에 올리면

Ornith를 불러오자 current_model_memory가 4.93GiB에서 9.85GiB로 올랐습니다. 로드된 모델이 0개로 내려간 뒤 두 모델을 차례로 다시 불러 확인했을 때도 합계는 10,580,138,442바이트(약 9.85GiB)로 같았습니다. 16GB 기기에서 모델 둘을 같이 올려 두는 건 가능했지만, 이 값에는 KV 캐시 증가분과 운영체제, 브라우저가 쓰는 몫이 들어 있지 않습니다. 컨텍스트를 길게 쓰는 작업에서는 이 여유가 줄어들 것이라고 보지만 이번에는 재지 않았습니다.

답변은 어땠나

코드 수정은 8개 답변(본 측정 6회, 로드 확인 2회)을 꺼내 실제로 실행했습니다. 전부 moving_average([1,2,3,4,5], 2)에서 [1.5, 2.5, 3.5, 4.5]를 반환했습니다. 수정은 8개 모두 range(len(xs) - k + 1)이었습니다. 원인 설명은 모델별로 한 답변씩 읽었는데 둘 다 끝 범위가 한 칸 모자라 마지막 윈도우가 빠진다는 점을 짚었습니다. 설명의 길이는 달랐습니다. 읽은 Qwen 답변은 원인 한 문장과 코드가 전부였고 읽은 Ornith 답변은 “원인”, “고친 코드” 제목을 달고 k=2일 때 range(4)가 된다는 검산까지 붙였습니다. 그 Ornith 답변의 원인 문장은 “len(xs)보다 k만큼 작아져서”처럼 다소 어색했습니다.

요약은 6개 모두 세 문장이었고 KV 캐시, 통합 메모리, 첫 토큰 시간, 초당 생성 토큰 수, 최대 메모리 사용량 같은 원문의 핵심 항목이 들어 있었습니다. 원문에 없는 수치를 만들어 낸 답변은 없었습니다. 눈에 띈 차이는 말투입니다. 프롬프트에 말투를 지정하지 않았는데 Qwen은 3회 모두 “~입니다”로, Ornith는 원문처럼 “~다” 평서체가 2회, “~입니다”가 1회였습니다. Qwen의 한 답변에는 “CPU 와 GPU 가”처럼 조사 앞을 띄운 곳이 있었습니다.

사고 모드를 켜 두면 생기는 일

처음에는 사고 모드를 끄지 않고 “한 문장으로 인사해 주세요.“만 보냈습니다. Qwen3.8-9B는 max_tokens를 200으로 줬는데 200토큰을 전부 “Thinking Process” 분석에 쓰고 finish_reason: length로 끝났습니다. 인사는 한마디도 나오지 않았습니다. 총 14.5초가 걸렸습니다. 그래서 본 측정은 chat_template_kwargs로 enable_thinking: false를 넣고 진행했습니다. 짧은 응답이 필요한 앱에서 이 모델을 쓴다면 이 설정이 필요하다고 봅니다. Ornith를 사고 모드를 켠 채로 돌려 보지는 않아서 같은 증상이 나는지는 모릅니다.

같은 요청을 되풀이했을 때

Qwen에 같은 요약 프롬프트를 연달아 보내자 첫 토큰 시간이 처음 1.60초에서 0.24초와 0.22초로 내려갔습니다. 서버가 앞부분 계산을 재사용한 결과로 보입니다. 같은 시스템 프롬프트를 계속 쓰는 에이전트라면 체감 속도는 표의 첫 토큰 시간보다 빠를 수 있습니다. 제가 이 글에서 표에 쓴 값은 모두 이 효과를 피한 값입니다.

로드 시간은 따로 봐야 합니다

Ornith의 51.5초와 6.2초 사이에는 8배 넘는 차이가 있습니다. 앞의 값은 그 세션에서 처음 읽은 것이고 뒤의 값은 로드된 모델이 0개로 내려간 뒤 다시 읽은 것입니다. 가중치는 외장 볼륨에 있습니다. 디스크 파일 캐시가 데워져 있었는지가 원인일 가능성이 높다고 보지만, purge 권한이 없어서 캐시를 비운 상태로 다시 재 보지는 못했습니다. Qwen은 이 비교용으로 처음 읽은 값을 따로 남기지 못했고 두 번 모두 3.6초였습니다. 두 모델의 로드 시간을 비교하는 용도로는 쓸 수 없는 숫자입니다.

이 기록으로 말할 수 없는 것

  • 8K 이상의 긴 입력에서의 첫 토큰 시간과 메모리 증가
  • 몇 분 이상 이어지는 부하에서 속도가 떨어지는지 여부
  • 사고 모드를 켠 Ornith의 동작
  • 코딩 에이전트 작업, 한국어 장문 작성 같은 실제 업무에서의 품질 차이
  • 두 모델 벤치마크 점수의 정확성

16GB Mac mini에서 9B급 4bit 모델이 돌아간다는 것과, 두 모델의 속도와 메모리가 같은 수준이라는 것까지가 이번에 확인한 범위입니다. 어느 쪽이 더 낫다는 판단은 긴 컨텍스트와 실제 작업으로 한 번 더 재야 할 수 있습니다.

같은 기기에서 27B 2bit 모델을 oMLX로 돌린 기록은 Bonsai 27B 실험에 있고, 거기서는 조건을 맞춘 뒤 oMLX가 약 5.1 tok/s였습니다. 더 큰 모델을 같은 기기에서 따져 본 기록은 MAI-Code-1.1-Flash 로컬 버전 메모리 계산에, 12B급을 먼저 돌려 본 기록은 Gemma 4 12B 첫 점검에 있습니다.

확인한 자료