제8회 「EVO-X2 (Ryzen AI Max+ 395 / 128GB) + Qwen3.8-Flash-Next를 만지다가, 금단의 llama.cpp 개조에 손을 대게 된 이야기」
제9회 「EVO-X2 (Ryzen AI Max+ 395 / 128GB) + Qwen3.8-Flash-Next용 llama.cpp 개조 재개로 베이스 리포지토리를 변경하여, ROCm 버전을 처음 빌드한 이야기」
제10회 「EVO-X2 (Ryzen AI Max+ 395 / 128GB) + Qwen3.8-Flash-Next용 llama.cpp 개조로 장 컨텍스트의 TG를 고속화한 이야기」
이어서.
제9회에서 베이스인 llama.cpp를 공식(upstream) 최신 버전(당시)으로 교체하고, 공식 llama.cpp 업데이트에 맞춰 베이스를 쉽게 교체할 수 있도록 환경을 정비했다. 이후 제10회에서는 Vulkan/ROCm 백엔드 처리의 프로파일링을 진행하여 장 컨텍스트의 TG를 고속화하는 수정 사항들을 몇 가지 적용했다.
다음 고속화 항목에 착수하려고 최신 정보를 조사하던 중, upstream 업데이트에서 유사한 기능이 이미 포함되어 있는 것을 발견했기 때문에, 독자적인 구현은 보류하고 베이스인 llama.cpp를 교체하기로 결정했다.
제9회의 방침 변경부터 제10회의 고속화 구현, 그리고 교체 판단까지 4일밖에 지나지 않았다. 구현의 대부분은 AI에게 의존하고 있다고는 하지만, 대체 뭘 하고 있는 건가 하는 기분도 든다.
이후로는 Prompt Processing(입력을 읽어들이는 처리)를 PP, Token Generation(출력 생성)을 TG로 표기한다. 둘 다 단위는 tok/s이며, 높을수록 빠르다는 의미이다.
이번에도 최신 upstream으로 교체했다고 해서 그대로 빨라지는 식의 단순한 이야기는 아니었다.
- 베이스를 업데이트하자 Vulkan 64k의 PP가 이전보다 느려졌다. 조사해 보니 MoE의 타일 선택 방법 변경이 큰 원인이었으며, 이 부분은 구 방식(old way)으로 전환했다.
- MTP는 upstream에 이미 구현되어 있었다. Unsloth의 MTP 드래프트를 사용하기 위한 작은 수정만으로 작동했지만, 처음에는 일부 조건에서 MTP ON일 때가 TG가 느리다라는 예상치 못한 결과가 나왔다. - 조사해 보니, dense MTP draft에서 사용하지 않는 indexer 관리와 target 측 QSA에서 불필요한 레이아웃 재구성이 발생하고 있었다. 2단계의 수정으로 TG 개선을 확인할 수 있었다.
- MTP ON 상태에서는 PP가 느려지는 문제가 여전히 남아있어, MTP를 통해 장문 요약 처리 전체가 빨라진 것은 아니었다.
소스 코드, Windows 빌드, 검증 스크립트 등은 여기서 공개하고 있다.
이번 작업은 r4/upstream-refresh-20261002 브랜치에서 진행했다.
속도와 관련된 부분만 먼저 다룬다.
| 항목 | 내용 |
|---|
| PC | GMKtec NucBox EVO-X2 |
| ... | |
| 세부 옵션과 모델의 차이는 각각의 비교에서 맞추고, 비교에 영향을 주는 것은 본문에도 기재한다. 자세한 내용은 기사 말미에 정리하겠다. | |
먼저 git worktree를 사용하여, 원본(upstream) llama.cpp에서 작업 시작 시점의 최신 버전(커밋 bed0a856606ee4a24a164066f73d2379447033f5) 소스를 로컬의 다른 폴더에 가져왔다.
git fetch upstream master --prune
git worktree add `
-b r4/upstream-refresh-20261002 `
...
그 후, 빌드 스크립트, 측정 스크립트 등 제9회에서 만든 스크립트들과 문서만 새 폴더에도 복사했다. 과거의 고속화 패치는 일단 아무것도 넣지 않고, 새로운 upstream 그대로의 성능을 측정하기로 했다.
Vulkan 버전과 ROCm 버전을 빌드하여, 항상 사용하던 일본어 논문 데이터를 이용해 llama-cli로 속도를 측정했다. 모델은 Unsloth Qwen3.8-Flash-Next UD-IQ3_XXS (PLE16 테이블 변환을 거치지 않은 오리지널 버전)였고, MTP는 OFF 상태였다.
| Backend | Context | PP tok/s | TG tok/s |
|---|
| Vulkan | 64k | 248.38 | 24.61 |
| ROCm | 64k | 369.72 | 20.98 |
| Vulkan | 128k | 168.09 | 22.81 |
| ROCm | 128k | 275.00 | 17.13 |
| ... | | | |
| 제9회에서 측정했던 이전 upstream(r3 초기) 수치가 여기 있습니다. 이 값은 PLE16 대응 패치를 적용하고 변환된 모델로 측정한 것이기 때문에, 엄밀히 말하면 동일한 바이너리/동일한 모델의 비교는 아닙니다. | | | |
| Backend | Context | PP tok/s | TG tok/s |
|---|
| Vulkan | 64k | 267.90 | 17.20 |
| ROCm | 64k | 357.93 | 14.86 |
| Vulkan | 128k | 159.56 | 12.00 |
| ROCm | 128k | 259.41 | 9.91 |
| ... | | | |
| 전반적으로 빨라진 느낌이지만, 자세히 보면 Vulkan 64k의 PP만 느려졌지 않나요? | | | |
267.90→248.38 tok/s로 약 7.3% 감소했습니다. 이것은 단순한 측정 오차는 아닌 것 같습니다.
그리고 TG는 이전 upstream 초기 값과 비교하면 상당히 빨라졌습니다. 하지만 제10회에서 고속화된 최종 값보다는 느립니다. 제10회의 최종 값은 256k에서 Vulkan이 21.69, ROCm이 17.37 tok/s였습니다. 이번에는 우리가 구현한 독자 패치를 이식하지 않았기 때문에 그럴 수도 있으니, 여기서는 깊게 파고들지 않겠습니다.
Vulkan 64k의 PP만 이전 버전보다 느려진 것이 신경 쓰여서 원인을 조사해 보았습니다.
먼저, 제10회에서도 사용했던 환경 변수 GGML_VK_PERF_LOGGER=1로 Vulkan 백엔드의 처리 시간을 측정했습니다. 신구 GPU 연산 시간을 비교했을 때, 차이가 큰 부분은 Flash Attention보다는 MoE의 행렬 곱셈이었습니다.
MoE(Mixture of Experts)에서는 토큰에 따라 일부 expert를 선택하여 실행합니다. 이때 그 행렬 곱셈을 Vulkan GPU로 넘길 때, 몇 줄씩 묶어서 계산할지, 데이터를 어떻게 정렬할지를 결정하는 것이 이번 타일(tile) 선택입니다. 선택 방식이 다르면, 계산하는 내용은 같더라도 GPU의 사용법이 바뀌어 속도에 영향을 미칩니다.
チャッピー님께 upstream 변경 사항을 조사해 달라고 요청한 결과, 94a0ae3e7 근처에서 MoE의 tile/alignment 선택이 바뀌었습니다. 변경된 부분을 조사하여 환경 변수로 신 방식과 구 방식을 전환할 수 있도록 수정했습니다.
GGML_VK_MOE_LEGACY_TILE_SELECTION |
선택 방법 |
|---|---|
0 (설정 안 했을 때도 동일) | 신 upstream 방식: expert당 평균 행 수를 기준으로 선택 |
1 | 구 방식: 전체 토큰 수를 기준으로 선택 |
수정하는 부분은 Vulkan의 MUL_MAT_ID에서 사용하는 tile/alignment 선택만입니다. expert 분배, QSA나 Flash Attention은 변경하지 않았습니다. 원래 upstream과 동일한 동작을 기본값으로 하고, 구 방식을 사용할 경우에만 명시적으로 환경 변수를 설정하도록 했습니다.
이번 PP에서는 예를 들어 1024 토큰을 처리하는 부분의 tile 선택이 신 방식에서는 20이었고, 구 방식에서는 1024로 바뀌었습니다. 먼저 64k에서 GPU profiler를 사용해 두 방식을 비교했을 때,
| 지표 | 신 upstream 방식 | 구 방식 | 변화 |
|---|
| PP GPU 연산 합계 | 253.097초 | 230.763초 | -8.82% |
| ... | | | |
| MoE만으로 약 16.13초 단축되었으며, 이는 PP GPU 연산 전체 단축분인 22.33초의 약 72%를 설명할 수 있습니다. | | | |
게다가 profiler를 OFF로 하고, 동일한 바이너리/동일 입력으로 신 방식 → 구 방식 → 구 방식 → 신 방식(ABBA)의 일반 측정을 진행했습니다.
| MoE tile 선택 | 64k PP tok/s | 64k TG tok/s |
|---|
| 신 upstream 방식 (A, 2회 평균) | 248.475 | 25.195 |
| 구 방식 (B, 2회 평균) | 268.985 | 24.565 |
**PP가 +8.25%**입니다. 새로운 upstream에서 이전 upstream보다 속도가 느려진 주된 원인은 이 MoE 타일 선택의 차이로 설명할 수 있을 것 같습니다.
TG는 짧은 128 토큰 생성에서는 측정값에 편차가 있어, 25.195 $ o$ 24.565의 차이를 이번 변경으로 인한 속도 저하라고 판단하지 않겠습니다.
이후 Vulkan의 주요 비교에서는 GGML_VK_MOE_LEGACY_TILE_SELECTION=1을 고정하기로 했습니다. 물론 이는 이번 검증 환경 및 조건에서의 결과이므로, 다른 GPU나 모델에서도 이전 방식이 빠르다고 단정할 수는 없습니다.
첫 번째 베이스를 만드는 데 예상외로 시간이 오래 걸렸지만, 다음번에는 제8회에서 적용했던 'PLE16 변환 테이블 대응', 'QSA grouped-union' 두 가지 변경 사항을 이식했습니다.
'PLE16 변환 테이블 대응'(COMMON-001)은 고속화가 주 목적이 아니라, 지금까지 사용해 온 Unsloth Qwen3.8-Flash-Next UD-IQ3_XXS의 PLE16 변환 모델을 새로운 upstream에서도 로드할 수 있도록 하기 위함입니다. 속도 향상 결과는 가능한 한 동일한 모델로 비교하고 싶기 때문에, 지금까지 측정에 사용했던 모델이 로드될 수 있도록 이것을 먼저 이식했습니다.
새로운 upstream의 loader 구조에 맞춰 이식하여, 오리지널 버전과 변환 버전 둘 다 작동하도록 했습니다.
64k에서 두 모델을 동일한 조건으로 측정해 본 결과는 다음과 같습니다:
| Backend | 모델 | PP tok/s | TG tok/s |
|---|
| Vulkan | 오리지널 | 267.51 | 24.53 |
| ... | | | |
PP는 Vulkan에서 약 0.5%, ROCm에서는 거의 차이가 없습니다. 앞서의 클린 측정 결과와 비교했을 때 Vulkan PP가 크게 올라간 것은, 방금 수정한 MoE 타일 선택을 이전 방식으로 고정했기 때문이며, PLE16 변환만의 효과는 아닙니다.
또 다른 'QSA grouped-union'(VULKAN-002)은 제8회에서 가장 효과적이었던 PP 고속화 항목입니다. 다만 새로운 upstream에서는 sparse Flash Attention이나 selected-id의 내부 구조가 바뀌었기 때문에, 그대로 cherry-pick하는 것이 아니라 변경된 구조에 맞춰 이식했습니다.
GGML_VK_QSA_UNION=0/1로 OFF/ON을 전환하며, MoE 타일 선택 등 다른 조건을 고정하여 비교한 결과는 다음과 같습니다:
| 모델 | Context | PP OFF | PP ON | PP 개선 | TG OFF | TG ON |
|---|---|---|---|---|---|
| 오리지널 | 64k | 269.08 | 337.45 | +25.41% | 24.72 | 24.82 |
| ... |+65.21% |
23.22 | 23.28 |
| PLE16 | 256k | 132.96 | 266.75 | +100.62% | 19.58 | 19.59 |
새로운 upstream에서도 긴 입력일수록 효과가 크고, TG는 거의 변하지 않았습니다. 이로써 Vulkan PP에 대해서는 일단 좋은 수준까지 되돌릴 수 있었습니다. 참고로, 이 기능 역시 소스의 기본값은 OFF 상태이며, 측정 시 환경 변수를 설정하여 ON으로 만들었습니다.
제10회에서 적용했던 TG 고속화 2건은 완전히 같지는 않지만, 유사한 주요 구조가 upstream에도 들어가기 시작했습니다. 이번 첫 측정 결과로 보아 완전히 같은 것이 들어간 것은 아닌 것 같지만, 아직 upstream 쪽에 움직임이 있을 것 같아서 이 2건의 이식은 일단 보류하기로 했습니다.
베이스를 교체하고 과거에 구현된 패치를 골라 이식하는 것만으로도, 이상하게 작동하지 않는지 하나씩 체크하기 위해 속도 측정과 출력 검사를 했더니 상당히 시간이 걸렸습니다.
이번의 메인 주제로서 MTP를 시도해 보기로 했습니다.
사실 제8회 기사를 올린 후, 제8회의 llama.cpp Strix Halo 전용 fork 기반 개조판에 Unsloth의 MTP 모델을 로드할 수 있게 하여, MTP를 적용하면 TG는 빨라지지만 PP가 느려지는 것을 어떻게든 해결하려고 했습니다. 하지만 시간이 다 되어 그대로 휴가를 떠났고, 휴가 후에는 최신 정보를 알아보느라 정신이 팔려서 완전히 잊고 있었습니다.
MTP(Multi-Token Prediction)는 메인 모델이 다음 토큰을 하나씩 확정하는 대신, 드래프트용의 작은 추가 모델로 앞선 토큰을 예측하고, 메인 모델이 한 번에 확인하는 방식입니다. 이 예측이 채택(accept)되면, 해당 부분에서 TG(Time Gauge)가 빨라지는 것이 기대됩니다. 다만, 드래프트 측에서도 계산과 캐시 관리가 필요하기 때문에, 채택률이 높다고 해서 반드시 빨라진다고는 할 수 없습니다.
휴가 전에 구현했던 MTP 대응 패치를 이식하기 위해 업스트림(upstream) 업데이트 정보를 조사해 보니,
c061df19838ff60970faf54fd7e414953590125d
Qwen4Exp: add MTP (#29761)
라는 변경 사항이 2026년 10월 1일에 업스트림에 병합(merge)되어 있었고, 이번 r4 베이스에도 이미 포함되어 있었습니다.
그래서 과거의 패치를 통째로 이식하는 대신, 우선 평소 사용하는 llama-cli에 드래프트 모델 지정 옵션을 추가하고, 손에 있는 Unsloth Q8_0 MTP 드래프트를 불러와 보기로 했습니다. 주요하게 추가된 옵션은 다음과 같습니다.
-md "(MTP 드래프트 GGUF의 경로)" \
--spec-type draft-mtp \
--spec-draft-n-max 2 \
...
그런데,
ggml-backend.cpp:345: GGML_ASSERT(buffer)
라는 오류로 멈추면서, 드래프트 모델 초기화에 실패했습니다. 역시 그렇게 쉽게 될 리가 없나 싶어 차피에게 원인을 조사해 달라고 부탁했습니다.
그 결과, 현재 사용하고 있는 Unsloth의 MTP 드래프트 모델은 QSA(Qwen Sparse Attention)가 아닌 dense attention을 사용하는 MTP 모델이었습니다. MTP 모델의 MTP layer 48의 compression ratio는 0이었고, 이 MTP 블록 자체는 indexer pool을 사용하지 않았습니다.
(QSA에 대해서는 제10회에서 간단히 설명했으므로 이번에는 생략합니다)
업스트림의 MTP graph에서는 메인 모델 전체에 indexer pool이 존재할 경우, 드래프트 모델 측에서도 사용하지 않는 pool 입력까지 setter에 등록하고 있었습니다. 그래프 내에서 미사용이었기 때문에 해당 입력 버퍼가 확보되지 않았고, setter가 그곳에 접근하여 assert 오류가 발생했던 것입니다.
그래서 MTP layer 자체의 compression ratio가 양수일 경우에만 pool 입력을 등록하도록 수정했습니다. 이렇게 하면 손에 있는 ratio=0 드래프트는 dense 상태로 작동하고, QSA 대응 MTP를 사용하는 경로는 변경하지 않아도 됩니다.
그대로는 작동하지 않았지만, 원래 전용 fork 기반의 MTP용 패치와 비교했을 때는 훨씬 간단한 수정으로 끝났습니다.
MTP 드래프트 모델을 불러올 수 있게 되었으니, 바로 Vulkan 버전과 ROCm 버전에 모두 걸쳐 속도를 측정해 보았습니다. MTP OFF/ON을 각각 2회씩 ABBA 순서로 비교했으며, 64k/128k/256k에서 512 토큰 생성, temperature 0.2, seed 1234 등의 조건을 통일했습니다. Vulkan은 먼저 구현했던 MoE tile 선택 구 방식과 QSA union을 ON으로 설정했습니다.
| Backend | Context | PP OFF → ON | TG OFF → ON | TG 증감 | MTP 채택률 |
|---|
| Vulkan | 64k | 334.68 → 310.19 | 25.75 → 29.84 | +15.9% | 72.8% |
| Vulkan | 128k | 294.27 → 266.09 | 23.27 → 23.02 | -1.1% | 67.0% |
| Vulkan | 256k | 265.02 → 228.08 | 19.76 → 15.25 | -22.8% | 67.2% |
| ROCm | 64k | 370.13 → 347.41 | 21.12 → 25.48 | +20.6% | 70.1% |
| ROCm | 128k | 277.57 → 260.15 | 16.88 → 17.73 | +5.1% | 69.6% |
| ROCm | 256k | 185.72 → 173.81 | 12.41 → 10.96 | -11.7% | 65.6% |
MTP를 적용하니 PP 속도가 느려지는 것은 이전부터 알고 있던 문제라 지금은 신경 쓰지 않기로 했지만, 고속화를 기대했던 TG에서도 Vulkan 128k에서는 효과가 사라졌고, 256k에서는 Vulkan/ROCm 모두 MTP를 넣는 것이 더 느리게 나왔다.
이대로라면 MTP의 의미가 없지 않은가?
3회차 때 같은 모델을 당시의 Vulkan 프리빌드 버전 llama.cpp로 측정했을 때는 128k까지 MTP를 적용하는 편이 빨랐고, 기사화하지 않은 휴가 전 전용 fork 기반 개조판으로도 MTP ON 상태에서 TG가 느려진 적은 없었다.
게다가 이번 Vulkan의 채택률은 128k에서 약 67.0%, 256k에서도 약 67.2%에 불과하다. 채택률이 급락한 것만으로는 이 속도 저하를 설명할 수 없다. Vulkan 256k에서는 MTP OFF와 ON 두 가지 결과가 모두 나왔기 때문에, 측정의 우연이라고 생각하기 어렵다.
간신히 MTP가 작동하게 되었지만, 이대로는 실용성이 제로이므로, MTP 전용 진단 로그 출력 기능을 추가하여 처리 시간을 드래프트 처리・타겟 측 검증・CPU에서의 레이아웃 관리 등으로 나누어 조사해 보았다.
특히 신경 쓰였던 부분은 QSA용 풀(pool)의 레이아웃을 CPU에서 재구성하는 처리였다. MTP ON 시 256k에서는 약 15.015초, 128k에서는 약 5.207초였던 것에 비해, 256k MTP OFF 때는 약 0.004초에 불과했다. 긴 이력을 가진 상태에서 추측 디코딩(speculative decoding)에 따른 시퀀스 관리 때마다 불필요한 작업이 발생하는 것 같았다.
다만, 이것은 MTP 전용 진단으로 측정한 CPU 레이아웃 처리 시간일 뿐, TG 시간 전체나 GPU 연산 시간 전체는 아니다.
가장 먼저 주목한 것은 드래프트(draft) 측이었다. 앞서 확인했듯이, 현재 가지고 있는 Unsloth MTP 드래프트는 compression ratio=0의 Dense Attention이므로 QSA indexer의 풀을 사용하지 않는다.
그런데 메모리 구축 측은 메인 모델에 QSA가 있기 때문에, 사용할 필요가 없는 드래프트 측에도 인덱서 캐시와 풀 레이아웃 관리를 가지고 있었다. 추측 디코딩에서는 채택/미채택 여부에 따라 이력을 수정하기 때문에, 이 불필요한 관리가 여러 번 작동한다.
그래서 대상이 단일 MTP 블록의 Dense Draft인 경우 등만 확인되면, 그 불필요한 인덱서 확보・관리를 생략할 수 있도록 했다. 환경 변수는 LLAMA_MTP_SKIP_DENSE_INDEXER=1이다. 기본값은 OFF다. QSA를 사용하는 MTP 드래프트나 메인 모델 측의 인덱서는 그대로 유지했다.
256k, 512 토큰 생성, 동일 바이너리로 A=LLAMA_MTP_SKIP_DENSE_INDEXER OFF(0), B=ON(1)으로 하여 ABBA 일반 측정을 진행했다.
| dense draft indexer 생략 | PP tok/s | TG tok/s |
|---|
| OFF (A, 2회 평균) | 228.835 | 15.220 |
| ON (B, 2회 평균) | 229.650 | 20.185 |
TG는 15.220 → 20.185 tok/s로 (+32.62%) 증가했다. PP는 거의 변함이 없다.
상당히 빨라졌지만, 아까 측정한 MTP OFF의 19.76 tok/s와 큰 차이가 없어, MTP 효과를 내기 위해서는 좀 더 개선할 부분이 필요하다.
수정 1로 드래프트 측의 무식은 제거했지만, 이번에는 메인 모델(target) 측에 아직 레이아웃 재구축 시간이 남아 있었다.
원인을 조사해 보니, MTP 검증 후에 시퀀스에서 불필요한 토큰을 제거하는 처리 과정에서, 실제로는 인덱서 풀에 속한 셀 수가 변하지 않았음에도 불구하고, 레이아웃을 '오래되었다'고 간주하여 무효화(invalidate)하는 경우가 있었다. 그러면 다음 처리 시 레이아웃을 처음부터 다시 만들어야 했다.
그래서 삭제 전후의 실제 풀 멤버십 카운트(pool membership count)를 비교하여, 변화가 없는 경우에 한해서 새로운 무효화를 생략하도록 했다. 물론, 실제로 셀이 줄어든 때는 기존처럼 무효화하고, 이미 무효화된 상태를 임의로 해제하는 일은 없다.
이 수정은 LLAMA_QSA_SKIP_NOOP_INVALIDATION=1으로 활성화하며, 기본값은 OFF다. 먼저 진단 로그 출력 ON의 256k 비교에서는,
⚠️ [IMG:N] 형식 토큰은 이미지 placeholder 입니다. 번역하지 말고 원래 위치에 그대로 유지하세요.
| target 측 처리 | 수정 전 | 수정 후 |
|---|
| full layout rebuild | 217회 | 95회 |
| ... | | |
| となった。無駄な再構築がかなり減っている。なお、抑制した123回のうち最後の1回は次のdecodeがないため、実際に減ったfull rebuildは122回(217→95回)になる。 | | |
次に診断出力ログをOFFにして、수정 1은 ON으로 고정한 채 수정 2만 OFF/ON을 전환하는 ABBA 일반 측정(通常計測)을 진행했다. Vulkan 256k, 입력 255,181 tokens, 생성 512 tokens, MoE tile 선택 구 방식과 QSA grouped-union 모두 ON.
| 실행 | 수정 2 | PP tok/s | TG tok/s | accepted / drafted |
|---|
| A1 | OFF | 230.73 | 20.24 | 293 / 436 |
| ... | A 평균 | | | |
| OFF | 229.940 | | | |
| 20.205 | | | | |
| 293 / 436 | | | | |
| B 평균 | | | | |
| ON | 229.565 | | | |
| 22.830 | | | | |
| 293 / 436 | | | | |
TG는 20.205 → 22.830 tok/s ( +12.99%)이다. PP는 약 -0.16%로, 거의 변함이 없다. 네 번 모두 생성 본문이 일치했고, 채택 수(採用数)도 293/436 (약 67.2%)으로 일치했다. 이번 Vulkan 256k에서는 수정 2가 TG 개선으로 이어졌다고 판단해도 좋을 것 같다.
ROCm에서도 두 가지 수정을 사용한 256k 측정을 진행했다. 비교한 것은 MTP OFF, MTP ON + 수정 1 ON + 수정 2 OFF (A), MTP ON + 수정 1 ON + 수정 2 ON (B)의 세 조건이다. 이쪽은 각 1회의 일반 측정이다.
| 조건 | PP tok/s | TG tok/s | PP 시간 | TG 시간 | PP+TG |
|---|
| MTP OFF | 185.76 | | | | |
| 12.39 | 1373.72초 | 41.25초 | 1414.97초 | | |
| A:MTP ON/수정 2 OFF | 173.91 | 13.57 | 1467.31초 | 37.65초 | 1504.95초 |
| B:MTP ON/수정 2 ON | 173.84 | 14.44 | | | |
| 1467.90초 | 35.39초 | 1503.29초 | | | |
B의 TG는 MTP OFF 대비 약 +16.5%이다. 당초 ROCm 256k에서는 MTP ON 쪽이 느렸기 때문에, 최종 구성에서는 TG 역전 현상이 더 이상 보이지 않게 되었다.
다만, ROCm에서는 약간 주의가 필요했다. A와 B에서 생성 본문이 일치하지 않았고, 채택 수도 289/441과 286/448로 달랐다. 이 때문에 13.57→14.44 tok/s라는 차이를 수정 2만의 순수한 고속화 효과라고 단정할 수는 없다.
여기까지로, 이번 EVO-X2 + Qwen3.8-Flash-Next의 256k 조건에서는 당초 관측했던 MTP ON에서 TG가 느려지는 현상을 상당히 개선할 수 있었다. 다만 ROCm의 수정 2 효과나, 128k를 포함한 다른 조건에서의 재현성에는 미확인 부분이 남아있다.
하지만, 예전부터 남아있던 MTP를 넣으면 PP가 느려지는 문제는 해결되지 않았다.
현재 속도 측정에 사용하고 있는
Vulkan 256k에서는 두 가지 수정 각각에서 TG 개선을 확인할 수 있었습니다. ROCm에서도 동일한 측정 내에서 최종 구성의 TG는 OFF를 상회했지만, 생성 결과에 차이가 있었기 때문에 수정 2 단독의 성능 효과에 대해서는 추가 확인이 필요합니다.
TG가 빨라져도 PP(Prompt Processing) 저하는 남아 있습니다. 이 문제는 별도로 조사할 계획입니다.
사실 이 기사를 작성하고 있는 시점에서는 베이스 교체부터 MTP를 시도한 실험을 시작한 지 약 1주일 정도가 지났습니다. 그 사이에 여러 가지 주제로 벗어났다가, 겨우 llama.cpp의 MTP 검증으로 돌아온 상황이었습니다.
다음번에는 llama-cli뿐만 아니라 llama-bench에서도 측정해 보려고 했으나 결과가 일치하지 않아, 이 과정에서 벗어난 이야기를 쓰려고 합니다.
일반적인 llama-cli에서의 테스트 예시는 다음과 같습니다 (모델과 입력 파일 경로는 대체해야 합니다). 이는 MTP OFF의 예시입니다.
.\llama-cli.exe \
-m "(메인 모델 경로)" \
-c (컨텍스트 길이) \
...
MTP ON에서는 위에 다음을 추가했습니다.
-md "(MTP 드래프트 GGUF 경로)" \
--spec-type draft-mtp \
--spec-draft-n-max 2 \
...
※실제 측정 스크립트에서는 MTP OFF 시 --spec-type none도 명시하고 있습니다. 위는 명령어를 이해하기 위한 예시이며, 초기 baseline이나 MoE A/B 등 생성 토큰 수나 모델 배치가 다른 측정들도 포함됩니다. 512 token의 예를 모든 측정에 공통되는 유일한 명령어라고 간주하지 마시고, 각 절에 기재된 비교 조건을 우선해 주시기 바랍니다.
Vulkan 후반부 측정에서는 주로 다음 환경 변수를 지정했습니다. 독자적인 변경 사항은 모두 기본값으로 설정되어 강제 ON 처리하지 않았습니다.
$env:GGML_VK_MOE_LEGACY_TILE_SELECTION = '1'
$env:GGML_VK_QSA_UNION = '1'
$env:LLAMA_MTP_SKIP_DENSE_INDEXER = '1'
...
이번에도 이전과 동일한 일본어 장문 입력을 사용했습니다.
| context | 실 입력 |
|---|
| 64k | 61,789 tokens |
| ... | |