Kernel NEON giúp llama.cpp nhanh gấp 6,5 lần trên Arm
Một repository mới bổ sung kernel NEON dùng lệnh SDOT cho routine tiled K-quant matmul của llama.cpp, giúp tốc độ xử lý prompt trên CPU Arm tăng tới 6,5 lần với các định dạng lượng tử hóa thấp như IQ3_M, IQ3_XS và IQ2_M. Tốc độ sinh token và các định dạng đã có kernel Arm thì không thay đổi.

Trên CPU Arm, llama.cpp xử lý prompt chậm hơn nhiều so với x86 khi chạy các mô hình lượng tử hóa bit thấp. Nguyên nhân nằm ở một routine duy nhất: tiled K-quant matmul được thêm vào tháng 9/2026 (PR #27851 của llama.cpp, tác giả jbooth cùng bartowski) chỉ có kernel bên trong cho AVX-512 VNNI, AVX2 và AVX.
Trên Arm, những định dạng không có kernel riêng — gồm Q3_K, Q2_K và cả họ IQ — phải rơi về một đường xử lý giải nén lại trọng số cho mỗi phép tích vô hướng. Một repository mới đã bổ sung kernel NEON còn thiếu và đo hiệu năng trên ba thế hệ lõi máy chủ Arm.
Kết quả đáng chú ý nhất là 6,5 lần với định dạng IQ3_M trên Neoverse N1, khi xử lý prompt 512 token bằng Qwen2.5 1.5B với 4 luồng. Tuyệt đối, tốc độ tăng từ 11,5 lên 74,5 token/giây.
Kết quả đo theo từng định dạng
Bảng dưới đây là tốc độ xử lý prompt (token/giây), trước -> sau, kèm mức tăng:
| Định dạng | Neoverse N1 | Neoverse V1 | Neoverse V2 |
|---|---|---|---|
| IQ3_M | 11,5 -> 74,5 (6,5x) | 16,6 -> 80,8 (4,9x) | 21,7 -> 92,6 (4,3x) |
| IQ3_XS | 12,6 -> 74,9 (5,9x) | 18,9 -> 79,9 (4,2x) | 21,7 -> 91,9 (4,2x) |
| IQ2_M | 13,9 -> 60,6 (4,4x) | 21,5 -> 70,6 (3,3x) | 22,2 -> 78,8 (3,5x) |
| Q3_K_M | 31,4 -> 64,5 (2,1x) | 42,3 -> 76,6 (1,8x) | 44,2 -> 85,5 (1,9x) |
| Q2_K | 25,7 -> 50,6 (2,0x) | 36,1 -> 62,5 (1,7x) | 41,4 -> 71,6 (1,7x) |
| IQ4_XS | 41,0 -> 74,3 (1,8x) | 49,2 -> 80,3 (1,6x) | 54,0 -> 92,3 (1,7x) |
| Q4_K_M | 69,7 -> 70,5 (1,0x) | 90,1 -> 89,8 (1,0x) | 97,2 -> 96,4 (1,0x) |
| Q5_K_M | 58,0 -> 57,9 (1,0x) | 75,7 -> 75,7 (1,0x) | 83,9 -> 83,8 (1,0x) |
| Q6_K | 57,7 -> 57,6 (1,0x) | 92,7 -> 92,6 (1,0x) | 90,3 -> 89,7 (1,0x) |
| Q8_0 | 98,7 -> 100,9 (1,0x) | 108,7 -> 111,6 (1,0x) | 127,8 -> 127,7 (1,0x) |
Các định dạng đã có kernel Arm từ trước — Q4_K_M, Q5_K_M, Q6_K, Q8_0, thông qua đường "repack" cũ hơn — không thay đổi. Tốc độ sinh token (generation) cũng giữ nguyên ở mọi định dạng và mọi chip vì routine này không tham gia vào quá trình sinh.
Thay đổi gồm những gì
Bản vá thêm 164 dòng vào hai file của llama.cpp v0.9.0, trong thư mục ggml/src/ggml-cpu/tiled/:
tiled-kernel.cpp: thêm kerneltiled_run_microtile_neon, bậc dispatch của nó và một biến thểtiled_repack_src1dành cho Arm.tiled.cpp: chỉ một dòng, để routine được bật khi trình biên dịch nhắm tới CPU Arm có phần mở rộng dot-product (điều kiện__aarch64__và__ARM_FEATURE_DOTPROD).
Hai điểm thiết kế chính:
- Trừ bias ngay khi nạp. Với mọi định dạng được hỗ trợ, mã trọng số trừ đi bias vừa khít trong một byte có dấu. Kernel trừ bias ngay lúc nạp mã, nên chỉ một phép tích vô hướng có dấu là ra luôn tổng đã hiệu chỉnh bias, hoàn toàn bằng số học nguyên chính xác.
- Xen kẽ phía prompt. 16 hàng của một band được xen kẽ tại chỗ, theo đúng bố cục group-local mà kernel VNNI đang dùng. Nhờ vậy một lệnh SDOT-by-lane cộng bốn tổng riêng phần cho bốn hàng khác nhau và không cần bước rút gọn nào sau đó. Mỗi lượt, bốn hàng trọng số nhân với mười sáu hàng prompt được tích lũy trong mười sáu thanh ghi.
Driver khả chuyển, định dạng tile, các bộ giải nén và bài test đều là của upstream và không bị sửa.
Cách áp dụng và kiểm chứng
Bản vá được cung cấp dưới dạng file diff, kèm cả các file nguyên bản đã vá trong src/patched/, và riêng kernel trong src/neon_kernel.inc cùng script chèn nó vào file gốc của v0.9.0 (tools/apply_neon.py). Quy trình cơ bản là clone llama.cpp ở tag v0.9.0, áp dụng patch/arm-tiled.diff, build llama-bench và test-tiled-mulmat, rồi chạy test.
Phần benchmark so sánh trước và sau trên cùng một binary: biến môi trường GGML_CPU_TILED_MM=0 tắt routine, tương đương đường đi mặc định của llama.cpp v0.9.0 trên Arm.
Về kiểm chứng, bài test test-tiled-mulmat của chính llama.cpp đối chiếu kernel mới với đường tham chiếu, đạt 15/15 case trên Neoverse N1, V1 và V2. Chế độ ngẫu nhiên --fuzz đạt 88/88 case trên cả ba chip, với log trong results/bench/. Một lượt rà soát từng dòng không phát hiện lỗi tính đúng đắn.
Tác giả cũng ghi rõ những việc chưa làm: chưa có kỹ sư kernel thực thụ review, chưa chạy test-backend-ops, chưa build với sanitizer, và chưa so sánh đầu ra mô hình trên prompt thật.
Giới hạn cần lưu ý
- Đo trên máy chủ Arm thuê (AWS Graviton2, 3 và 4), không đo trên điện thoại. Lệnh SDOT có mặt từ lõi Cortex-A55 và A75 trở đi, nhưng điện thoại có cache nhỏ hơn, bộ nhớ chậm hơn, lõi không đồng nhất và bị giới hạn nhiệt.
- Chỉ thử một mô hình nhỏ (Qwen2.5 1.5B), 4 luồng, prompt 512 token.
- Chỉ xử lý prompt nhanh hơn; sinh token không dùng routine này.
- Con số trên Arm vẫn thấp hơn đáng kể so với mốc tham chiếu x86, và chưa rõ yếu tố nào đang là nút thắt.
- Chỉ thử ở 4 và 8 luồng. Người review dự đoán lợi ích sẽ giảm, thậm chí đảo chiều, khi vượt khoảng 16 luồng.
- Chưa thử Apple Silicon, Windows on Arm, batch từ 8 đến 32 token, hay các lõi cache nhỏ.
- Chỉ dùng NEON 128-bit; chưa dùng SVE và I8MM.
Về tương quan với các dự án khác: ik_llama.cpp đã có kernel NEON riêng cho những định dạng này (chưa benchmark ở đây); một bản port các kernel đó đã được merge vào unslothai/stable-diffusion.cpp ngày 9 và 10/11/2026 (PR #48 và #75), cũng chưa benchmark; và tác giả của routine gốc đang có PR mở #30200 tách nó thành các thư mục theo kiến trúc. Bản vá này nhắm v0.9.0 nên sẽ cần rebase nếu layout đó được merge.
Trạng thái với upstream
Bản vá chưa được gửi lên llama.cpp. Quy định đóng góp của dự án (AGENTS.md, CONTRIBUTING.md) chỉ cho phép mã do AI tạo ra khi có người đóng góp thực sự hiểu từng dòng và tự viết pull request bằng lời của mình, đồng thời yêu cầu các agent tự động không được submit. Ai đáp ứng được các điều kiện đó thì có thể tiếp tục đưa thay đổi này đi xa hơn.
Về nguồn gốc: routine tiled, driver và bài test là của jbooth cùng bartowski (PR #27851). Kernel Arm, phần benchmark và repository được viết, kiểm thử và đo ngày 11/11/2026 bởi Nikita — một AI agent trên Orbofi chạy Claude Fable 5.2 — trên các máy chủ AWS Graviton thuê. llama.cpp dùng giấy phép MIT; các file trong src/patched/ có nguồn gốc từ đó.
Góc nhìn
Với các đội đang chạy mô hình ngôn ngữ cục bộ trên hạ tầng Arm — từ máy chủ Graviton trên cloud đến các thiết bị biên — đây là kiểu tối ưu đáng để theo dõi. Xử lý prompt là giai đoạn tốn thời gian nhất khi làm việc với ngữ cảnh dài, RAG hay các tác vụ đọc tài liệu, nên mức tăng 4-6 lần ở các định dạng bit thấp có thể thay đổi đáng kể chi phí tính toán và độ trễ cảm nhận. Điều đáng chú ý là lợi ích tập trung đúng vào những định dạng tiết kiệm bộ nhớ nhất, vốn là lựa chọn phổ biến khi chạy trên phần cứng hạn chế.
Tuy vậy, cần thận trọng trước khi đưa vào production: chưa có review của kỹ sư kernel, chưa chạy sanitizer, chưa so sánh chất lượng đầu ra trên prompt thật, và chưa đo trên điện thoại hay Apple Silicon. Các đội kỹ thuật nên tự benchmark trên đúng phần cứng và đúng định dạng lượng tử hóa mình dùng, đặc biệt nếu chạy nhiều luồng hơn 8, trước khi coi đây là một khoản tiết kiệm chắc chắn.
Nguồn: github.com · Quy trình biên tập


