M4 Max에서 Qwen3.8 Flash Next를 oMLX로 돌리면 128K에서도 초당 56토큰

M4 Max 128GiB에서 oMLX 정식 0.7.0과 MLX oQ4e 가중치로 32K씩 누적해 128K까지 쟀습니다. 생성은 128K에서도 56 tok/s 안팎, 새로 넣는 32K의 prefill은 780 tok/s 안팎이었습니다.

2026-10-09 갱신: 처음 글은 릴리스 후보 oMLX 0.7.0rc1로 잰 결과였습니다. 정식 0.7.0으로 같은 구성을 다시 3회 재서 대표 수치를 바꿨습니다. 128K 생성이 44.89~45.24에서 55.66~56.96 tok/s가 됐습니다. rc1 결과는 아래에 남겨 두었습니다.

같은 M4 Max에서 Qwen3.8 Flash Next를 llama.cpp(GGUF) 대신 oMLX(MLX)로 돌려 봤습니다. 가중치는 MLX용 oQ4e 양자화(Jundot/Qwen3.8-Flash-Next-oQ4e-mtp)입니다. 결과는 llama.cpp 구성보다 2배 가까이 빨랐습니다. 128K까지 문맥을 쌓아도 생성은 초당 56토큰 안팎을 유지했고, prefill도 깊이에 따라 거의 느려지지 않았습니다.

GGUF와는 가중치 파일이 달라서, 같은 파일끼리 비교한 M4 Max와 Ryzen AI Max+ 395 비교 글에는 넣지 않고 따로 정리합니다.

55.66~56.96 tok/s128K 위치 생성 (MTP off, 3회)
779~781 tok/s128K 지점 추가 prefill
약 42초128K 지점 첫 토큰까지
0.7.0oMLX 버전 (정식)

측정 방식

공통 벤치마크 작업과 같은 틀입니다. 이탈리아어 소설과 C++ 코드 원문을 한 세션에서 32K토큰씩 이어 붙여 128K까지 채우고, 단계마다 새로 넣은 약 32K의 prefill 속도와 이어서 생성한 256토큰의 속도를 쟀습니다. 소설과 코드 각각 3회입니다.

가중치는 HF revision 2615fc0e입니다. 이 저장소는 10월 11일에 가중치 21개를 모두 다시 양자화한 판으로 바꿨고, 10월 24일에 한 번 더 다시 양자화했습니다. 지금 내려받는 파일은 이 글에서 잰 것과 다릅니다. 새 가중치로는 아직 재지 않았습니다.

설정은 MTP off, greedy(seed 42), thinking off, 요청 하나씩입니다. 모델의 PLE 테이블은 SSD에 두었고, KV 압축(TurboQuant)과 oMLX의 메모리 보호 기능은 껐습니다.

차이가 하나 있습니다. oMLX는 직전 단계에서 생성한 256토큰을 다음 prefill에서 다시 처리합니다. 토큰 ID와 누적 출력은 그대로 유지하지만 캐시 재사용 개수가 llama.cpp와 달라서, 이 결과는 같은 작업의 변형으로 기록했습니다.

정식판 3회는 10월 7일부터 9일까지 사흘에 걸쳐, 회차마다 서버를 새로 띄워 쟀습니다. 코드 3회차는 다른 프로세스의 디스크 부하가 감지돼 두 번 중단됐고, 세 번째에 끝까지 쟀습니다. 중단된 회차는 통계에서 뺐습니다.

결과: 정식 0.7.0, MTP off

누적 문맥 소설 prefill 소설 생성 코드 prefill 코드 생성
32K 806.19 59.20 819.92 57.34
64K 807.82 56.91 803.39 55.80
96K 796.23 56.04 786.43 55.43
128K 780.60 55.66 779.20 56.96

단위는 tok/s, 3회 평균입니다. 표준편차는 prefill이 0.9~6.4, 생성이 0.2~3.0 tok/s였습니다. 첫 토큰까지는 모든 단계에서 40~42초였습니다.

같은 기계에서 llama.cpp로 같은 모델(GGUF UD-Q4_K_XL)을 잰 현재 대표 결과(b11617, 10월 15~16일)는 128K 생성이 29.15~29.24 tok/s, 추가 prefill이 515~520 tok/s입니다. 가중치가 달라서 이 차이를 런타임만의 효과로 볼 수는 없지만, 이 기계에서 실제로 쓸 수 있는 속도로는 oMLX 구성이 생성 약 1.9배, prefill 약 1.5배 빨랐습니다. 10월 16일 전의 llama.cpp 대표 결과(128K 생성 15.25~15.51 tok/s)와 비교했을 때는 생성이 3.6~3.7배였는데, llama.cpp 쪽 대표가 바뀌면서 차이가 줄었습니다.

처음 측정(0.7.0rc1)과 비교

9월 30일에 릴리스 후보 0.7.0rc1로 잰 128K 값입니다. 가중치, PLE 배치, MTP off, 입력과 생성 길이는 같았습니다.

128K rc1 (9/30, 3회) 정식 0.7.0 (10/7~9, 3회)
소설 생성 44.89 55.66
코드 생성 45.24 56.96
소설 prefill 775.02 780.60
코드 prefill 769.95 779.20

prefill은 거의 같고 생성은 정식판이 초당 11토큰가량 높았습니다. 다만 8K로 먼저 확인했을 때 같은 입력에 대한 출력이 rc1과 정식판에서 서로 달랐습니다. 같은 계산이 빨라진 것인지는 이 측정으로 가릴 수 없어서, 정식판이 몇 퍼센트 빨라졌다고 말하지 않습니다.

개발 중인 main으로 잰 값 (참고)

10월 13일에 아직 배포되지 않은 oMLX main(98c7a2a)으로 같은 방식을 소설·코드 각 3회 다시 쟀습니다. 0.7.1.dev1 배포본의 네이티브 커널에 main의 Python 소스를 올린 구성이고, 가중치는 같은 2615fc0e, PLE는 mmap 오프로드, MTP는 off입니다.

누적 문맥 소설 생성 코드 생성
32K 62.18 62.19
64K 59.02 59.00
96K 57.76 57.79
128K 58.33 58.39

단위는 tok/s, 3회 평균이고 표준편차는 0.05~0.23입니다. 128K 생성이 정식 0.7.0보다 1.4~2.7 tok/s 높았습니다. 정식 배포가 아니고 여러 변경이 한꺼번에 들어 있어서, 대표값은 정식 0.7.0으로 둡니다. 같은 개발판에서 MTP2를 켜면 코드 128K 생성이 52.40 tok/s로 MTP off보다 낮았습니다.

이후 10월 26일 개발 릴리스 0.7.3.dev2와 10월 28일 릴리스 후보 0.7.3rc1로도 같은 가중치, 같은 방식으로 소설·코드 각 3회를 쟀습니다. 128K 생성은 dev2가 소설 56.81·코드 58.32, rc1이 54.26·57.71 tok/s로, 정식 0.7.0과 1.4 tok/s 안쪽 차이였습니다. 두 판의 출력 토큰은 서로 같았습니다. 측정을 맡은 쪽은 dev2 값을 이 구성의 대표로 두고 있지만, 이 글은 정식 배포판인 0.7.0을 대표로 유지합니다.

MTP3를 켜면 (rc1)

아래는 rc1에서 잰 것이고, 정식판에서는 긴 문맥으로 다시 재지 않았습니다. 코드 입력에서 적응형 MTP(최대 3토큰 draft)를 켰습니다.

누적 문맥 prefill 생성
32K 777.13 47.50
64K 771.98 45.31
96K 756.92 51.21
128K 746.66 58.40

128K에서 생성은 58.40 tok/s로 같은 rc1의 MTP off(45.24)보다 빨랐습니다. 그런데 한 요청 전체에 걸린 시간은 MTP off 48.62초, MTP3 48.69초로 같았습니다. 새로 넣는 32K의 prefill이 시간 대부분을 차지하고, 출력이 256토큰뿐이라 생성에서 아낀 시간이 묻혔습니다. 96K에서는 MTP3 생성 편차가 ±5.73 tok/s로 컸습니다. 출력이 길어지면 MTP의 이득이 커질 수 있지만 이 시험만으로 얼마나 커질지는 말할 수 없습니다.

8K 코드 선별에서는 MTP3가 약 76 tok/s까지 나왔는데, 긴 문맥에서는 그만큼 나오지 않았습니다. MTP는 출력 내용에 따라 수락률이 달라서 짧은 시험값을 긴 작업에 그대로 적용하면 안 됩니다.

그 밖에 확인한 것 (rc1)

  • 테이블 일부(PLE)를 RAM에 올려 두면 오히려 느렸습니다. 128K 생성이 약 27 tok/s로 SSD에 둘 때(약 45 tok/s)보다 낮았고, swap 증가로는 설명되지 않았습니다. 원인은 아직 모릅니다.
  • 4bit KV 압축(TurboQuant4)은 요청했지만 실제로는 적용되지 않았습니다. 이 모델의 attention 구조가 보존해야 하는 상태 때문에 압축이 꺼졌고, 4bit KV의 성능과 품질은 재지 못했습니다.
  • MTP off는 같은 입력에서 3회 출력이 정확히 같았고, MTP3는 64K부터 회차마다 출력이 달라졌습니다.

출력 정확도나 긴 문맥 회상은 rc1과 정식판 모두 재지 않았습니다.