M5 Ultra 256GB에서 oMLX로 DeepSeek V4.1 Flash 1017, 128K 위치 생성 초당 57~58토큰
Mac Studio M5 Ultra 256GB에서 oMLX 0.7.3.dev2와 MLX oQ2e 가중치로 DeepSeek V4.1 Flash 1017을 128K까지 누적해 쟀습니다. 생성은 같은 기계의 ds4보다 1.5배 빨랐고 prefill은 절반 수준이었습니다. MTP는 원문에 따라 0.76~1.72배였습니다.
앞선 ds4 글에서 잰 Mac Studio M5 Ultra(GPU 80코어, 통합 메모리 256GB)에서, 이번에는 MLX 기반 추론 서버 oMLX로 DeepSeek V4.1 Flash 1017을 돌렸습니다. 가중치는 mlx-community가 MLX 형식으로 양자화한 oQ2e-mtp(291.8GiB)입니다. 작업은 같습니다. 소설과 코드 원문을 32K토큰씩 이어 붙여 128K까지 가며, 단계마다 새로 넣은 부분의 prefill과 이어지는 256토큰 생성을 원문마다 3회 쟀습니다. 대표 결과는 MTP를 끈 것이고, MTP를 켠 결과는 따로 적었습니다. 두 설정 모두 24단계가 실패나 재측정 없이 끝났습니다.
이 결과를 읽을 때 알아 둘 것
- HTTP가 아니라 엔진을 직접 불렀습니다. oMLX의 HTTP
/v1/completions는 텍스트만 받고 EOS를 무시하는 옵션이 없어서, 토큰 ID로 정확히 256토큰을 생성시킬 수 없습니다. 그래서omlx serve의 시작 경로(설정 로드, 메모리 관리자 포함)를 그대로 거친 뒤 HTTP 대신 엔진 코어에 토큰 ID 요청을 넣었습니다. 첫 토큰 시간은 엔진이 첫 토큰을 만든 시각까지입니다. ds4 글처럼 HTTP로 잰 다른 글의 표와는 나란히 놓지 않습니다. - 단계 사이의 캐시 재사용이 공통 벤치마크 기준과 다릅니다. 기준은 앞 단계의 입력과 생성 토큰까지 재사용하는 것인데, oMLX는 앞 요청의 입력에서 마지막 토큰을 뺀 만큼(32767토큰 등)만 재사용하고 앞 단계가 생성한 256토큰을 다시 prefill합니다. 그래서 64K부터 단계마다 32,769가 아니라 33,025토큰을 처리합니다. 설정으로 바꿀 수 없는 엔진 구조이고, M4 Max의 oMLX 글에서도 같은 방식이었습니다. prefill 속도는 실제로 처리한 토큰 수로 나눴으므로 왜곡되지 않고, 첫 토큰 시간만 0.8% 정도 길어집니다.
- 엔진은 GitHub에 올라온 개발 릴리스 0.7.3.dev2입니다. 안정판이 아닙니다.
- 모델 파일이 ds4 글과 다릅니다. 아래 ds4와의 비교는 같은 파일 비교가 아니라 엔진과 양자화를 묶은 참고 비교입니다.
사용 설정
| 항목 | 값 |
|---|---|
| 기계 | Mac Studio, M5 Ultra (GPU 80코어), 256GB, macOS 27.0.1 |
| 엔진 | oMLX 0.7.3.dev2 (GitHub 릴리스 wheel), MLX 0.32.4 |
| 모델 | mlx-community DeepSeek-V4.1-Flash-1017-oQ2e-mtp (HF revision e528ef9f), 파일 53개 해시를 모두 대조 |
| 배치 | 본체 가중치는 메모리 상주, Engram 표(122.9GB)는 SSD에서 mmap으로 읽음. 전부 올리면 메모리 한도(239.1GB)를 넘는다고 oMLX가 정한 배치 |
| 캐시 | 엔진 기본 V4.1 캐시(KV 양자화 없음), prefix 캐시는 메모리 32GB + SSD. SSD 캐시는 회차마다 새로 만들고 지움 |
| prefill | 단위 2,048(기본), 속도 우선으로 고정 |
| MTP | 대표는 off, 비교는 on (모델에 들어 있는 초안 층, 적응형 깊이 최대 3) |
| 생성 | greedy, seed 42, 256토큰, EOS 무시, 채팅 템플릿 없이 원문 이어쓰기 |
| 반복 | 회차마다 새 프로세스, 짧은 인사로 워밍업한 뒤 측정 |
생성 속도는 256 ÷ (첫 토큰 → 256번째 토큰)으로 계산했습니다. ds4 글의 계산식과 가장 가깝습니다.
결과 (MTP off)
소설
| 문맥 | prefill (tok/s) | 첫 토큰 (초) | 생성 (tok/s) |
|---|---|---|---|
| 32K | 866.00 | 37.88 | 57.83 |
| 64K | 873.62 | 37.80 | 59.64 |
| 96K | 793.93 | 41.61 | 56.72 |
| 128K | 747.61 | 44.18 | 56.69 |
코드
| 문맥 | prefill (tok/s) | 첫 토큰 (초) | 생성 (tok/s) |
|---|---|---|---|
| 32K | 1094.93 | 29.93 | 59.66 |
| 64K | 945.52 | 34.93 | 58.64 |
| 96K | 845.75 | 39.05 | 58.75 |
| 128K | 808.95 | 40.82 | 58.35 |
3회 평균입니다. 생성은 문맥이 길어져도 거의 줄지 않았습니다(소설 57.8 → 56.7, 코드 59.7 → 58.4). 토큰 사이 간격은 가장 길어도 0.022초였고, 첫 디코딩이 멈추는 현상도 없었습니다. 세 회차의 출력 토큰은 단계마다 모두 같았습니다.
prefill은 소설 첫 회차의 32K·96K가 다른 두 회차보다 낮았습니다(827.5·772.8, 나머지는 약 885·804). 본 측정 전 같은 설정의 시험 실행에서는 32K가 752로 더 낮았습니다. Engram 표를 SSD에서 mmap으로 읽기 때문에, OS 파일 캐시에 얼마나 올라와 있느냐에 따라 prefill이 달라지는 것으로 보지만 확인하지는 않았습니다. 코드는 회차 차이가 1% 안이었습니다.
같은 기계의 ds4와 나란히 (참고)
같은 기계에서 ds4로 잰 대표 결과(antirez Q2 GGUF, 일반 가중치 전부 상주, MTP off)와 놓으면 방향이 반대로 갈립니다.
| 원문 | 문맥 | prefill oMLX / ds4 | 배율 | 생성 oMLX / ds4 | 배율 |
|---|---|---|---|---|---|
| 소설 | 32K | 866.00 / 1560.02 | 0.56 | 57.83 / 38.19 | 1.51 |
| 소설 | 64K | 873.62 / 1525.34 | 0.57 | 59.64 / 37.85 | 1.58 |
| 소설 | 96K | 793.93 / 1489.76 | 0.53 | 56.72 / 37.42 | 1.52 |
| 소설 | 128K | 747.61 / 1455.11 | 0.51 | 56.69 / 37.03 | 1.53 |
| 코드 | 32K | 1094.93 / 1777.88 | 0.62 | 59.66 / 38.25 | 1.56 |
| 코드 | 64K | 945.52 / 1670.98 | 0.57 | 58.64 / 37.90 | 1.55 |
| 코드 | 96K | 845.75 / 1607.96 | 0.53 | 58.75 / 37.48 | 1.57 |
| 코드 | 128K | 808.95 / 1590.56 | 0.51 | 58.35 / 37.06 | 1.57 |
생성은 oMLX가 1.51~1.58배 빨랐고, prefill은 ds4의 0.51~0.62배였습니다. 128K 지점에서 첫 토큰까지는 oMLX 41~44초, ds4 21~23초입니다.
이 차이를 엔진만의 차이로 읽으면 안 됩니다. 섞여 있는 것이 여럿입니다.
- 가중치가 다릅니다. oQ2e는 본체 168.9GiB(평균 2.69bpw, 전문가는 affine 2bit), antirez Q2는 151.8GiB(IQ2_XXS/Q2_K)입니다. Engram 표는 둘 다 디스크에서 읽지만 형식이 다릅니다.
- 토크나이저가 같은 원문을 조금 다르게 자릅니다. 소설은 904번째 토큰, 코드는 46,111번째 토큰부터 갈립니다. 그래서 131,072토큰이 덮는 원문 범위가 약간 다르고, 두 엔진의 출력을 토큰 단위로 대조할 수 없습니다.
- 측정 경계가 다릅니다. oMLX는 엔진 코어 요청, ds4는 C 세션 API이고, oMLX는 64K부터 단계마다 256토큰을 더 처리합니다.
prefill 차이가 어디서 오는지도 아직 모릅니다. oMLX의 prefill 단위를 2,048, 4,096, 8,192로 바꿔 소설을 한 번씩 재 봤지만 차이가 ±4% 안이었고 방향도 일정하지 않았습니다. ds4가 8,192 단위를 쓴다는 것으로는 설명되지 않습니다. 전문가 행렬곱 커널, Engram 읽기, attention 가운데 무엇인지는 재지 않았습니다.
MTP를 켜면
DeepSeek V4.1 가중치에는 다음 토큰 여러 개를 미리 짐작하는 초안 층(MTP)이 들어 있고, oMLX는 이를 써서 한 번에 여러 토큰을 검증할 수 있습니다. 나머지 설정은 그대로 두고 MTP만 켜서 같은 작업을 다시 3회씩 쟀습니다.
| 원문 | 문맥 | 생성 (tok/s) | MTP off 대비 | 검증 1회당 토큰 | 수락률 |
|---|---|---|---|---|---|
| 소설 | 32K | 43.78 | 0.76배 | 1.44 | 37.6% |
| 소설 | 64K | 71.91 | 1.21배 | 2.42 | 45.1% |
| 소설 | 96K | 47.43 | 0.84배 | 1.55 | 44.5% |
| 소설 | 128K | 46.39 | 0.82배 | 1.55 | 43.0% |
| 코드 | 32K | 102.77 | 1.72배 | 3.71 | 95.9% |
| 코드 | 64K | 84.60 | 1.44배 | 3.16 | 88.4% |
| 코드 | 96K | 79.97 | 1.36배 | 3.01 | 88.0% |
| 코드 | 128K | 90.93 | 1.56배 | 3.32 | 86.5% |
원문에 따라 결과가 갈렸습니다. 코드는 다음 토큰을 잘 맞혀 검증 한 번에 3토큰 이상이 받아들여졌고, 생성이 1.36~1.72배 빨라졌습니다. 소설은 검증 한 번에 1.4~1.6토큰에 그쳐 초안과 검증 비용을 메우지 못했고, 64K 단계를 빼면 오히려 16~24% 느려졌습니다. prefill은 MTP off와 1% 안에서 같았습니다.
MTP를 켜면 greedy인데도 출력이 달라졌습니다. 소설 32K는 20번째 토큰부터, 코드 64K는 76번째 토큰부터 MTP off와 다른 토큰이 나왔습니다. 여러 토큰을 한 번에 검증하는 계산이 한 토큰씩 디코딩하는 계산과 비트 단위로 같지 않아, 확률이 거의 같은 후보 사이에서 선택이 바뀌는 것으로 보지만 확인하지는 않았습니다. 출력이 갈린 다음 단계부터는 입력도 달라지므로(소설 64K부터, 코드 96K부터) 그 단계들의 on/off 비교는 같은 입력 비교가 아닙니다. MTP를 켠 세 회차끼리는 출력이 같았습니다.
검증 한 번에 나온 토큰들은 같은 시각에 찍히기 때문에, 같은 계산식이라도 MTP 쪽이 조금 후하게 나옵니다(코드 32K에서 약 1.5%, 소설에서 약 0.6%). 이렇게 원문에 따라 손익이 갈리기 때문에 MTP를 켠 값은 이 기계의 대표 결과로 쓰지 않았습니다.
측정 환경
측정하는 동안 메모리 여유는 가장 낮을 때 21%였고, swap은 2.38MiB에서 MTP 측정이 끝날 때 218MiB로 조금씩 늘었습니다. 다른 추론 서버는 측정 전에 내렸고 화면 공유는 켜져 있었습니다. 대표 6회 동안의 CPU 표본은 수집 방식의 실수로 전부 0으로 찍혀서, 그 구간에 다른 부하가 있었는지는 확인하지 못했습니다. 고친 뒤의 MTP 측정 구간에서는 커널 프로세스(최대 132%, Engram 읽기로 추정) 말고 계속 보인 부하가 없었습니다.
범위
출력 품질은 재지 않았습니다. 같은 oQ2e 파일을 쓰는 다른 MLX 서버(mlx-serve)와 Engram 표까지 메모리에 올리는 구성은 아직 재지 않았습니다. Engram을 메모리에 더 올리는 쪽이 prefill의 회차 차이와 ds4와의 prefill 차이를 가를 첫 후보입니다.