Split View: llama.cpp가 브라우저로 들어왔다 — WebGPU 백엔드가 16개 기기에서 측정한 천장
llama.cpp가 브라우저로 들어왔다 — WebGPU 백엔드가 16개 기기에서 측정한 천장
- 들어가며 — 브라우저 LLM은 데모를 졸업했나
- LlamaWeb — llama.cpp에 WebGPU 백엔드가 붙는다는 것
- 진짜 제약은 VRAM이 아니라 탭이다
- 숫자 — 기존 브라우저 프레임워크 대비
- 천장의 이름 — subgroup matrix
- 네이티브와의 거리 — 2.5배에서 10배
- 안전은 공짜가 아니다 — bounds check의 청구서
- 양자화는 여기서 속도 기술이 아니다
- 브라우저 편차 — Firefox는 아예 뺐다
- 실무자를 위한 정리 — 기능 감지와 폴백
- 그래서, 당신은 브라우저에서 LLM을 돌려야 하나
- 마치며
- 참고 자료
들어가며 — 브라우저 LLM은 데모를 졸업했나
브라우저에서 LLM을 돌린다는 아이디어는 몇 년째 데모의 영역에 있었습니다. WebLLM이 Llama를 탭 안에서 굴리는 걸 보여줬고, Transformers.js가 파이프라인 API를 웹으로 옮겼고, 그때마다 "서버 비용 0원, 데이터는 기기 밖으로 안 나감"이라는 슬로건이 따라붙었습니다. 슬로건은 사실입니다. 문제는 그다음 질문 — 그래서 실제로 얼마나 느리고, 얼마나 먹고, 어디서 깨지나 — 에 답하는 공개 데이터가 거의 없었다는 겁니다. 대부분의 벤치마크는 저자의 노트북 한 대에서 나왔고, 브라우저 GPU 성능은 하드웨어를 건너 일반화되지 않습니다.
그래서 2026년 5월 20일에 arXiv에 올라온 Llamas on the Web이 흥미롭습니다. UC Santa Cruz의 Reese Levine, Tyler Sorensen 등이 쓴 이 논문은 llama.cpp의 WebGPU 백엔드(논문에서는 LlamaWeb이라 부릅니다)를 만들고, 8개 벤더의 GPU 16개에 걸쳐 모델 10개와 가중치 포맷 4종을 실측했습니다. 저자들 표현으로는 "단일 추론 엔진 프레임워크 안에서 수집된 가장 큰 크로스-디바이스·크로스-모델 평가 데이터셋"이고, 모바일 기기를 포함한 유일한 WebGPU 데이터셋입니다.
이 글은 그 데이터가 말하는 2026년 브라우저 추론의 현실을 정리합니다. 결론부터 말하면 — 좋아졌지만, 천장이 어디인지가 아주 또렷하게 보입니다. 그리고 그 천장에는 이름이 있습니다.
LlamaWeb — llama.cpp에 WebGPU 백엔드가 붙는다는 것
먼저 지형을 정리해 둡시다. WebGPU는 브라우저가 여러 네이티브 API 위에 얹는 추상화입니다 — macOS/iOS에서는 Metal, Linux와 Android에서는 Vulkan, Windows에서는 DirectX. 그리고 브라우저마다 구현이 다릅니다. Chromium 계열은 Dawn, Firefox는 wgpu, Safari는 WebKit 자체 구현을 씁니다. 여기서 중요한 사실 하나 — Dawn은 C++로 쓰여 있어서 브라우저 밖 네이티브 코드에서도 쓸 수 있습니다. 이 사실이 뒤에서 결정적으로 작동합니다.
기존 브라우저 추론 프레임워크는 각자 다른 엔진에 기대고 있었습니다. Transformers.js는 ONNX Runtime, WebLLM은 MLC-LLM. 반면 LlamaWeb은 llama.cpp 본체에 WebGPU 백엔드를 붙이는 길을 택했습니다. 이게 왜 중요하냐면, llama.cpp의 자산 — 특히 GGUF 양자화 포맷 생태계 전체 — 를 그대로 상속받기 때문입니다.
이 작업의 핵심 조각 중 하나는 llama.cpp 본체에 이미 머지되어 있습니다. PR #17031("ggml webgpu: faster matrix multiplication/matrix-vector multiplication", reeselevine, 2025년 11월 8일 머지)이 행렬 곱 커널 두 벌을 넣었습니다. PR 설명을 그대로 옮기면 이렇습니다 — 하나는 레지스터 타일링, 다른 하나는 "subgroup matrices"(텐서 코어 내지 최적화된 subgroup 루틴에 접근하게 해 주는 WebGPU 기능)를 쓰는 구현이고, "현재 subgroup matrices는 실험적이며, 지원하지 않는 기기에서는 레지스터 타일링 방식으로 폴백"합니다.
이 폴백 문장을 기억해 두십시오. 이 글의 나머지가 전부 저 문장에 관한 이야기입니다.
진짜 제약은 VRAM이 아니라 탭이다
브라우저 추론을 처음 다루는 사람이 가장 자주 틀리는 지점이 여기입니다. 제약은 GPU 메모리가 아니라 탭 프로세스입니다.
논문이 지적하는 기존 프레임워크의 첫 번째 문제가 메모리 비효율입니다. LlamaWeb은 시작 시점에 메모리를 정적으로 할당해서, 프롬프트 길이나 생성 길이와 무관하게 메모리 사용량이 예측 가능하도록 만들었습니다. 여러 커널이 필요로 하는 중간 저장 공간까지 모델이 처음 돌기 전에 미리 잡아 둡니다.
더 재미있는 건 모델 로딩 쪽입니다. 저자들은 wllama(원래 llama.cpp를 WASM으로 CPU에서만 돌리던 라이브러리)에 WebGPU 지원을 얹으면서 로딩 경로를 다시 짰습니다. 가중치를 OPFS(Origin Private File System)에 저장해 두고, WebAssembly 힙에는 절대 실체화하지 않습니다. 이유가 논문 각주에 있는데, 읽고 나면 고개가 끄덕여집니다.
WASM 힙은 grow-only이기 때문에, 한 번 탭이 할당한 메모리는 그 탭이 살아 있는 동안 해제될 수 없다.
즉 로딩 과정에서 잠깐 2GB를 만졌다가 놓아도, 그 2GB는 탭이 닫힐 때까지 돌아오지 않습니다. 그래서 LlamaWeb은 llama.cpp의 비동기 로딩 인터페이스를 써서 1MB 버퍼 4개만으로 OPFS에서 WebGPU 버퍼로 가중치를 스트리밍합니다. 논문은 "Safari가 특히 메모리 사용 한도가 엄격해서, 이 변경 덕분에 더 크고 강력한 모델을 서빙할 수 있었다"고 적고 있습니다.
결과 수치는 이렇습니다. Chrome과 Safari, 여러 기기 조합에서 잰 피크 메모리 기준으로 기존 브라우저 추론 프레임워크 대비 평균 29~33% 감소. f16 Llama3.2 1B 기준 네 개 테스트 구성의 정규화 피크 메모리 기하평균으로는 WebLLM보다 49%, Transformers.js보다 41% 낮았습니다. 구성별로 뜯어보면 편차가 큽니다 — Apple M4 Pro의 Chrome에서는 WebLLM 대비 13%, Transformers.js 대비 16% 개선에 그쳤지만, 같은 기기의 Safari에서는 28%와 59%로 벌어집니다.
다만 이 큰 격차 중 일부는 LlamaWeb이 잘해서가 아니라 상대가 새는 것입니다. 논문은 Safari에서 Transformers.js의 메모리가 10GB까지 올라가다 탭이 죽었고, 이는 메모리 누수로 보인다고 명시합니다. RTX 5080 Windows 구성에서는 WebLLM이 같은 식으로 10GB 근처까지 올라갔습니다. 벤치마크를 읽을 때 이런 문장을 지우면 안 됩니다 — "61% 적게 쓴다"의 상당 부분은 상대의 버그입니다.
숫자 — 기존 브라우저 프레임워크 대비
성능 비교는 NVIDIA RTX 5080, Apple M4 Pro, AMD RX 7900 XT, Intel Arc B580 네 개 GPU에서 이뤄졌습니다. 벤더가 전부 다르다는 게 핵심입니다.
디코드(토큰 생성) 처리량에서 LlamaWeb은 WebLLM보다 약 54%, Transformers.js보다 69% 높았습니다. 논문 초록의 표현으로는 "네 개 GPU에 걸쳐 디코드 처리량 45~69% 향상"입니다.
그런데 prefill(프롬프트 처리)은 반대입니다.
그러나 prefill 성능은 뒤처지는데, LlamaWeb은 WebLLM 처리량의 49%, Transformers.js 처리량의 79%에 그친다.
절반도 안 됩니다. 논문이 원인을 두 가지로 짚습니다. 첫째, WebLLM은 TVM의 정교한 커널 융합(kernel fusion)을 써서 작은 연산마다 모델 가중치를 반복해서 읽어 오는 비용을 줄입니다. 둘째, Transformers.js는 행렬 곱을 subgroup으로 최적화한 구현을 갖고 있습니다.
여기서 브라우저 LLM의 성격이 드러납니다. 디코드는 메모리 대역폭에 묶인 행렬-벡터 곱이 지배하고, prefill은 연산에 묶인 행렬-행렬 곱이 지배합니다. LlamaWeb은 전자를 잘 짰고, 후자에서는 아직 도구가 없습니다. 무슨 도구냐면 —
천장의 이름 — subgroup matrix
WebGPU에서 subgroup은 워크그룹 안의 스레드들이 SIMT 방식으로 서로 값을 주고받게 해 주는 기능입니다. 이건 이미 나왔습니다 — Chrome 134에서 데스크톱과 Android 모두 기본 활성화됐고(Chrome Platform Status), 그 뒤로도 조금씩 붙고 있습니다. subgroup_id가 Chrome 144, subgroup_uniformity가 Chrome 145, Subgroup Size Control이 Chrome 152 예정으로 잡혀 있습니다. (오늘 기준 stable은 Chrome 151이고, 152의 stable 예정일은 2026년 8월 25일입니다.)
그런데 subgroup matrix는 다릅니다. 이건 GPU의 고정 크기 행렬 곱 유닛 — NVIDIA로 치면 텐서 코어 — 을 직접 여는 기능이고, LLM의 prefill이 정확히 그 유닛을 원합니다. 논문의 커널 라이브러리에는 이걸 쓰는 sg_mat 커널이 이미 들어 있습니다. 문제는 이 한 문장입니다.
subgroup matrix 기능은 아직 stable 브라우저 릴리스에서 사용할 수 없지만, 일부 WebGPU 구현에서는 네이티브로 사용할 수 있다.
확인해 보면 정말 그렇습니다. Chrome Platform Status에서 subgroup 관련 항목은 Subgroups(134, 기본 활성화), subgroup_id(144), subgroup_uniformity(145), Subgroup Size Control(152 예정)이 전부고, subgroup matrix 항목 자체가 없습니다. Google이 표준화 그룹에 cooperative matrix 제안을 가져가겠다고 밝힌 단계이고, subgroups 때와 마찬가지로 논의의 상당 부분이 이식성(portability)에 관한 것이 되리라 예상되고 있습니다.
그래서 저자들은 우회로를 씁니다. Dawn이 C++ 라이브러리라는 앞의 사실을 기억하십시오 — 같은 백엔드 코드를 Dawn으로 네이티브 실행하면 subgroup matrix를 켤 수 있습니다. 그렇게 재면 그림이 뒤집힙니다.
실제로, LlamaWeb을 네이티브로 실행한 prefill 수치를 쓰면, LlamaWeb은 Apple M4 Pro의 WebLLM을 제외한 모든 기기에서 다른 프레임워크의 prefill 수치를 앞서며, WebLLM 대비 88%, Transformers.js 대비 205%의 기하평균 속도 향상을 보인다.
같은 코드, 같은 기기, 다른 실행 환경. 브라우저 안에서는 WebLLM의 49%, 브라우저 밖에서는 WebLLM의 188%. 지금 브라우저 LLM의 prefill 천장은 알고리즘도 커널 품질도 아니고, 표준화되지 않은 하드웨어 기능 하나입니다.
논문의 결론도 정확히 그 톤입니다 — 저자들은 "subgroup matrix가 아직 브라우저에 없지만, 일반 공급에 도달하면 LlamaWeb이 경쟁력 있는 prefill 성능을 제공할 좋은 위치에 있다"고 씁니다. "있다"가 아니라 "도달하면"입니다. 이 조건절을 지우고 읽으면 안 됩니다.
네이티브와의 거리 — 2.5배에서 10배
그럼 브라우저를 떠나 네이티브 백엔드와 비교하면 어떨까요. 논문은 llama.cpp의 llama-bench로 같은 기기에서 네이티브 백엔드를 돌려 비교합니다.
Fig. 5(a)에서 볼 수 있듯이, CUDA와 Vulkan 백엔드는 두 가중치 포맷 모두에서 여전히 WebGPU를 prefill에서 최대 10배, 디코드에서 2.5배 앞선다. 이는 텐서 코어의 더 나은 활용과 비동기 메모리 로드 같은 벤더 고유의 고급 기능에 대한 접근 때문으로 보인다.
Apple 쪽도 비슷합니다 — "Metal 백엔드는 항상 WebGPU 백엔드를 앞서며, prefill에서 2배 이상, 디코드에서 50%" 앞섭니다.
이 격차는 앞서 본 PR #17031의 벤치마크에서도 그대로 보입니다. PR 저자가 자기 M3에서 잰 예비 수치입니다(즉 벤더 보고가 아니라 저자 1인의 기기 한 대이고, llama-bench는 llama.cpp의 네이티브 CLI 벤치마크 도구입니다).
Llama-3.2-1B-Instruct, Apple M3, llama-bench (PR #17031 저자 측정, 예비 수치)
pp512 (t/s) tg128 (t/s)
F16 WebGPU 1014.17 ± 9.38 28.71 ± 0.19
F16 Metal 1368.47 ± 0.95 35.99 ± 0.78
Q4_0 WebGPU 960.52 ± 6.05 41.76 ± 0.62
Q4_0 Metal 1346.68 ± 1.21 103.92 ± 0.37
Q4_0의 디코드를 보십시오 — 41.76 대 103.92. 2.5배입니다. 앞의 논문 수치와 정확히 같은 자릿수의 격차가 독립적으로 재현됩니다.
하지만 방향이 한쪽만은 아닙니다. 이 부분이 논문에서 가장 놀라운 대목인데, WebGPU가 벤더 전용 백엔드를 이기는 경우가 실제로 있습니다.
- Apple에서 Vulkan 대비: MoltenVK를 거치는 Vulkan 백엔드 상대로 WebGPU가 prefill 최대 59%, 디코드 45% 더 빠릅니다.
- AMD에서: 네이티브 HIP 백엔드는 항상 WebGPU를 앞서지만, Vulkan 백엔드는 prefill에서 WebGPU에 3배 뒤집니다.
- Intel에서: 안전 검사를 끈 WebGPU 백엔드가 f16 모델 prefill에서 가장 빨라 SYCL 백엔드를 23% 앞서고, 같은 모델 디코드에서도 네이티브 SYCL을 10% 앞섭니다.
논문은 이걸 크로스-디바이스 튜닝의 효과로 해석합니다 — Linux에서 WebGPU의 하부 런타임이 바로 Vulkan인데도 LlamaWeb의 튜닝된 행렬 곱 파라미터가 Vulkan 전용 백엔드를 이기는 기기가 있다는 겁니다. "네이티브가 항상 빠르다"는 직관은 틀렸습니다. 정확히는, 잘 튜닝된 이식성 있는 구현이 튜닝 안 된 네이티브 구현을 이깁니다.
안전은 공짜가 아니다 — bounds check의 청구서
브라우저에서 WebGPU 컴파일러는 셰이더에 안전 검사를 삽입합니다. 버퍼 범위 밖 접근과 0으로 나누기를 막기 위해서입니다. 이건 협상 대상이 아닙니다 — 임의의 웹페이지가 당신의 GPU에 셰이더를 던지는 모델에서 이걸 뺄 수는 없습니다.
값이 얼마인지를 논문이 쟀습니다.
안전 검사를 켜고 끈 성능을 비교하면, prefill에서 q4_k_m 모델은 평균 14%, f16 모델은 평균 23% 느려지고, 디코드에서는 각각 5%와 1% 느려진다. 이 슬로다운은 NVIDIA RTX 5080의 prefill에서 최대 42%까지 도달했다.
즉 안전 검사는 prefill에서 두 자릿수를 먹고 디코드에서는 거의 공짜입니다. 이것도 일관된 그림입니다 — 연산 집약적인 행렬 곱 루프 안에서 인덱스마다 검사가 붙으니까요. 그리고 앞의 Intel 사례에서 WebGPU가 SYCL을 이긴 건 "안전 검사를 끈" 조건이었다는 걸 다시 보십시오. 브라우저에서는 그 조건을 쓸 수 없습니다.
논문의 향후 과제 목록에 이게 그대로 올라가 있습니다 — 더 나은 정적 분석으로 불필요한 bounds check를 제거하기, 저정밀도 실행에서 모델 안정성을 보장하도록 부동소수점 차이를 언어 수준에서 더 견고하게 다루기, 그리고 u16 같은 새 데이터 타입을 WebGPU에 추가해 더 효율적인 메모리 로딩과 네이티브 하드웨어 가속을 가능하게 하기.
양자화는 여기서 속도 기술이 아니다
이건 로컬 LLM을 다뤄 본 사람일수록 더 놀랄 대목입니다.
현재 상태로는 WebGPU에서의 llama.cpp 양자화는 주어진 모델을 실행하는 데 필요한 메모리 요구량을 줄이는 기술이지, 성능 최적화가 아니다.
CPU나 CUDA에서 q4를 쓰는 이유는 메모리도 메모리지만 대역폭을 아껴 속도를 얻기 위해서입니다. WebGPU에서는 그 등식이 성립하지 않습니다. 논문이 이유를 두 가지로 듭니다. 첫째, 범용 가중치 포맷의 역양자화(dequantization) 루틴이 복잡하고 연산이 무겁습니다 — WebGPU가 지원하지 않는 nvfp4 같은 하드웨어 네이티브 포맷과 달리 말입니다. 둘째, 현재의 레이아웃이 WebGPU가 대상으로 하는 다양한 GPU에서 성능에 최적화되어 있지 않을 수 있습니다.
앞의 M3 표가 이걸 뒷받침합니다 — Q4_0의 prefill(960)이 F16의 prefill(1014)보다 오히려 느립니다. 파일 크기는 2.30GiB에서 729.75MiB로 3분의 1 토막이 났는데도요. 디코드에서는 41.76 대 28.71로 양자화가 이기지만(여기선 대역폭이 지배하니까), prefill에서는 역양자화 비용이 이득을 먹어 버립니다.
실무적 함의는 단순합니다. 브라우저에서 양자화는 "안 돌아가던 모델을 돌아가게 만드는" 수단이지, "돌아가던 모델을 빠르게 만드는" 수단이 아닙니다. 그리고 앞 절에서 봤듯 브라우저의 진짜 제약이 탭 메모리라는 걸 생각하면, 그것만으로도 충분히 값을 합니다.
브라우저 편차 — Firefox는 아예 뺐다
이식성 이야기를 하는 글에서 이 문단을 빼면 정직하지 않을 겁니다.
Firefox가 WebGPU를 지원하긴 하지만, 우리는 상당히 낮은 성능을 관찰했다. 예컨대 Apple M3에서 q4_k_m 가중치의 llama 모델이 Firefox에서는 초당 약 1토큰, Chrome에서는 초당 약 52토큰으로 실행되어, 우리는 평가에서 Firefox 결과를 제외한다.
50배입니다. 논문은 Chrome과 그 하부의 Dawn이 "많은 플랫폼에서 일관되게 가장 높고 가장 안정적인 WebGPU 성능을 제공했다"고 적고, 그래서 별도 언급이 없는 한 모든 실험을 Chrome에서 돌렸습니다. iOS에서는 다른 브라우저 엔진을 쓸 수 없어서 Safari를 씁니다.
"WebGPU는 3대 브라우저 모두 지원한다"는 문장은 사실이지만, 프로덕션에서 그 문장에 기대면 다칩니다. 기능 지원(feature support)과 성능 동등성(performance parity)은 다른 축이고, 2026년 7월 현재 후자는 전혀 갖춰져 있지 않습니다.
부동소수점 쪽에도 지뢰가 있습니다. 논문 각주 하나를 보십시오 — 개발 중에 레지스터 타일링 커널에 f16 누산(accumulation)을 쓰면 Qwen2.5 같은 일부 모델이 Apple M 시리즈 GPU에서 횡설수설하는 출력을 냈고, 그래서 현재는 이 커널에 f32 누산을 씁니다. 저자들의 표현으로 "부동소수점 정밀도의 차이는 잘 알려진 문제이고, WebGPU와 그 응용 안에서 이를 다루는 것은 풀린 문제가 아닙니다". 크래시가 아니라 조용한 품질 저하로 나타난다는 게 이 부류 버그의 고약한 점입니다.
실무자를 위한 정리 — 기능 감지와 폴백
코드 수준에서 이 모든 게 의미하는 건 하나입니다. 기능을 가정하지 말고 물어보십시오.
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) throw new Error('WebGPU 어댑터 없음');
// subgroups 는 Chrome 134+ 에서 기본 활성화되지만,
// 어댑터가 실제로 광고하는지 확인하고 명시적으로 요청해야 한다.
const wanted = ['subgroups', 'shader-f16'].filter((f) => adapter.features.has(f));
const device = await adapter.requestDevice({ requiredFeatures: wanted });
// subgroup matrix 는 2026년 7월 현재 어떤 stable 브라우저에도 없다.
// 있는 척하는 코드를 쓰지 말고, 폴백 경로를 기본 경로로 취급할 것.
const hasSubgroups = device.features.has('subgroups');
LlamaWeb의 설계가 정확히 이 모양입니다 — sg_mat 커널이 있지만 기본 경로가 아니고, 지원하지 않는 기기에서는 레지스터 타일링으로 폴백합니다. PR #17031의 문장을 다시 옮기면 "지원하지 않는 기기에서는 코드가 레지스터 타일링 방식으로 폴백"합니다. 브라우저에서는 지금 그 폴백이 모든 기기의 경로입니다.
한 가지 더. 커널 컴파일은 모델의 첫 forward pass 동안 일회성 비용(약 1~5초)을 물고, 그 뒤로는 캐시된 파이프라인으로 실행됩니다. 첫 토큰 지연을 설계할 때 이 몇 초를 잊으면 안 됩니다.
그래서, 당신은 브라우저에서 LLM을 돌려야 하나
데이터를 놓고 보면 판단 기준이 꽤 선명해집니다.
값을 하는 경우
- 데이터가 기기를 떠나면 안 된다. 이게 거의 유일하게 압도적인 이유입니다 — 2.5배 느린 걸 감수할 만한 유일한 종류의 요구사항이니까요.
- 모델이 작다. 논문이 실측한 모델들은 0.23GB에서 3.11GB 사이입니다.
- 디코드가 지배적인 워크로드다 — 짧은 프롬프트, 긴 생성. 브라우저 추론이 상대적으로 가장 덜 지는 구간입니다.
- 배포가 URL 하나여야 한다. 설치도, 런타임도, 서버도 없다는 건 여전히 진짜 가치입니다.
아직 아닌 경우
- prefill이 지배적이다 — 긴 문서를 통째로 넣는 RAG 같은 패턴. 여기가 정확히 최대 10배가 벌어지는 구간이고, 고칠 기능은 아직 표준에 없습니다.
- Firefox를 지원해야 한다. 50배는 성능 이슈가 아니라 기능 이슈입니다.
- 정밀도에 민감하다. f16 누산이 조용히 출력을 망가뜨리는 사례가 이미 논문에 있습니다.
- 서버 GPU를 쓸 수 있고 프라이버시 제약이 없다. 그럼 그냥 서버에서 돌리십시오. 브라우저 추론은 비용 절감 기술이 아니라 프라이버시·배포 기술입니다.
마지막 항목이 제일 중요합니다. "서버 비용 0원"은 사실이지만, 그 대가로 당신은 사용자의 배터리와 2.5~10배의 성능과 8개 벤더의 하드웨어 편차를 받습니다. 그건 절감이 아니라 이전(transfer)입니다.
마치며
2026년 브라우저 LLM 추론의 상태를 한 문단으로 요약하면 이렇습니다. 엔지니어링은 성숙했습니다 — 정적 메모리 계획과 OPFS 스트리밍으로 메모리를 29~33% 줄였고, 튜닝된 이식성 커널이 어떤 기기에서는 벤더 전용 네이티브 백엔드를 이깁니다. 그런데 prefill에서는 여전히 네이티브에 최대 10배 지고, 그 이유의 큰 덩어리가 텐서 코어를 여는 표준 기능이 없어서입니다. 같은 코드를 브라우저 밖에서 그 기능과 함께 돌리면 순위가 뒤집힌다는 게 그 증거입니다.
그래서 이 이야기의 교훈은 "브라우저가 아직 느리다"가 아닙니다. 오히려 반대입니다 — 병목이 이제 소프트웨어가 아니라 표준화 일정에 있다는 것. subgroups가 원본 제안에서 Chrome 134 기본 활성화까지 대략 1년의 origin trial을 거쳤다는 걸 생각하면, cooperative matrix가 아직 제안 단계라는 사실이 무엇을 의미하는지는 분명합니다. 그리고 그 논의의 중심은 성능이 아니라 이식성이 될 겁니다 — 8개 벤더의 행렬 유닛을 하나의 안전한 API로 덮는 일이 원래 어려우니까요.
그때까지, 브라우저에서 LLM을 돌리는 결정은 성능 결정이 아니라 프라이버시 결정입니다. 그 프레임으로 접근하면 이 기술은 지금도 충분히 값을 합니다. 벤치마크 프레임으로 접근하면 실망할 겁니다.
참고 자료
- Llamas on the Web: Memory-Efficient, Performance-Portable, and Multi-Precision LLM Inference with WebGPU (Levine 등, arXiv:2605.20706, 2026년 5월 20일)
- ggml webgpu: faster matrix multiplication/matrix-vector multiplication — llama.cpp PR #17031 (2025년 11월 8일 머지)
- WebGPU Subgroups — Chrome Platform Status (Chrome 134 기본 활성화)
- WebGPU
subgroup_idfeature — Chrome Platform Status (Chrome 144) - What's New in WebGPU — Chrome for Developers 릴리스 노트 색인
- WebAssembly and WebGPU enhancements for faster Web AI, part 2 — cooperative matrix 제안 배경
- WebGPU 컴퓨트 셰이더와 WGSL 실습(관련 글)
llama.cpp Comes to the Browser — The Ceiling a WebGPU Backend Measured Across 16 Devices
- Introduction — Has Browser LLM Graduated From Demo?
- LlamaWeb — What It Means to Bolt a WebGPU Backend Onto llama.cpp
- The Real Constraint Isn't VRAM — It's the Tab
- The Numbers — Against Existing Browser Frameworks
- The Name of the Ceiling — subgroup matrix
- The Gap to Native — 2.5x to 10x
- Safety Isn't Free — The Bounds-Check Bill
- Quantization Here Isn't a Speed Trick
- Browser Variance — Firefox Got Dropped Entirely
- For Practitioners — Feature Detection and Fallback
- So, Should You Run an LLM in the Browser
- Closing
- References
Introduction — Has Browser LLM Graduated From Demo?
The idea of running an LLM in the browser has lived in demo territory for years. WebLLM showed Llama running inside a tab; Transformers.js ported the pipeline API to the web; and every time, the same slogan came along — "zero server cost, data never leaves the device." The slogan is true. The problem is that almost no public data answered the next question: so how slow is it really, how much does it eat, and where does it break? Most benchmarks came from a single author's laptop, and browser GPU performance doesn't generalize across hardware.
That's what makes Llamas on the Web, posted to arXiv on May 20, 2026, interesting. Written by Reese Levine, Tyler Sorensen, and others at UC Santa Cruz, the paper builds a WebGPU backend for llama.cpp (the paper calls it LlamaWeb) and measures it across 16 GPUs from 8 vendors, 10 models, and 4 weight formats. In the authors' words, it's "the largest cross-device, cross-model evaluation dataset collected within a single inference-engine framework," and the only WebGPU dataset that includes mobile devices.
This post lays out what that data says about the reality of browser inference in 2026. The conclusion up front — it's gotten better, but the ceiling is now very clearly visible. And that ceiling has a name.
LlamaWeb — What It Means to Bolt a WebGPU Backend Onto llama.cpp
Let's lay out the terrain first. WebGPU is an abstraction the browser layers on top of several native APIs — Metal on macOS/iOS, Vulkan on Linux and Android, DirectX on Windows. And each browser implements it differently. Chromium-based browsers use Dawn, Firefox uses wgpu, and Safari uses its own WebKit implementation. One important fact here — Dawn is written in C++, so it can also be used from native code outside the browser. That fact turns out to be decisive later on.
Existing browser inference frameworks each leaned on a different engine — Transformers.js on ONNX Runtime, WebLLM on MLC-LLM. LlamaWeb instead chose to bolt a WebGPU backend directly onto llama.cpp itself. Why that matters: it inherits llama.cpp's assets wholesale — especially the entire GGUF quantization format ecosystem.
One of the key pieces of this work is already merged into llama.cpp proper. PR #17031 ("ggml webgpu: faster matrix multiplication/matrix-vector multiplication", reeselevine, merged November 8, 2025) added two matrix-multiplication kernels. To carry the PR description over directly — one uses register tiling, the other uses "subgroup matrices" (a WebGPU feature that gives access to tensor cores, or optimized subgroup routines), and "subgroup matrices are currently experimental, and fall back to the register tiling approach on unsupported devices."
Keep that fallback sentence in mind. Everything else in this post is really about that one sentence.
The Real Constraint Isn't VRAM — It's the Tab
This is where people new to browser inference most often get it wrong. The constraint isn't GPU memory — it's the tab process.
The first problem the paper identifies in existing frameworks is memory inefficiency. LlamaWeb allocates memory statically at startup, making memory usage predictable regardless of prompt length or generation length. It even reserves the intermediate storage various kernels need before the model runs for the first time.
The more interesting part is model loading. The authors rewrote the loading path while adding WebGPU support to wllama (originally a library that ran llama.cpp CPU-only via WASM). Weights are stored in OPFS (Origin Private File System), and never materialized into the WebAssembly heap at all. The reason is in a paper footnote, and once you read it, it makes sense.
Because the WASM heap is grow-only, memory a tab has once allocated cannot be released for as long as that tab is alive.
In other words, even briefly touching 2GB during loading and then letting go doesn't give that 2GB back until the tab closes. So LlamaWeb uses llama.cpp's asynchronous loading interface to stream weights from OPFS to WebGPU buffers using just four 1MB buffers. The paper notes that "Safari in particular has tight memory limits, and this change let us serve larger, more capable models."
The resulting numbers: measured peak memory across Chrome and Safari on multiple device combinations, an average 29–33% reduction versus existing browser inference frameworks. By the geometric mean of normalized peak memory across four test configurations on f16 Llama3.2 1B, it came in 49% lower than WebLLM and 41% lower than Transformers.js. Broken down by configuration, the spread is wide — on Chrome on an Apple M4 Pro it was only a 13% improvement over WebLLM and 16% over Transformers.js, but on Safari on the same device that widens to 28% and 59%.
However, part of this large gap isn't LlamaWeb doing well — it's the competition leaking. The paper explicitly states that on Safari, Transformers.js's memory climbed to 10GB before the tab died, which looks like a memory leak. On the RTX 5080 Windows configuration, WebLLM climbed to roughly 10GB the same way. When reading benchmarks, don't erase sentences like this — a good chunk of "uses 61% less" is the other side's bug.
The Numbers — Against Existing Browser Frameworks
The performance comparison was run on four GPUs: NVIDIA RTX 5080, Apple M4 Pro, AMD RX 7900 XT, and Intel Arc B580. The key point is that every vendor is different.
On decode (token generation) throughput, LlamaWeb was about 54% higher than WebLLM and 69% higher than Transformers.js. In the paper abstract's phrasing, it's a "45–69% improvement in decode throughput across four GPUs."
But prefill (prompt processing) runs the other way.
Prefill performance, however, lags — LlamaWeb reaches only 49% of WebLLM's throughput and 79% of Transformers.js's throughput.
Less than half. The paper points to two causes. First, WebLLM uses TVM's sophisticated kernel fusion to cut the cost of repeatedly re-reading model weights for every small operation. Second, Transformers.js has an implementation that optimizes matrix multiplication using subgroups.
This reveals the character of browser LLMs. Decode is dominated by memory-bandwidth-bound matrix-vector multiplication; prefill is dominated by compute-bound matrix-matrix multiplication. LlamaWeb has built the former well, and for the latter it still lacks a tool. What tool? —
The Name of the Ceiling — subgroup matrix
In WebGPU, subgroups are a feature that lets threads within a workgroup exchange values with each other in a SIMT fashion. This has already shipped — enabled by default on both desktop and Android in Chrome 134 (Chrome Platform Status), with more being added bit by bit since. subgroup_id is slated for Chrome 144, subgroup_uniformity for Chrome 145, and Subgroup Size Control for Chrome 152. (As of today, stable is Chrome 151, and 152's scheduled stable date is August 25, 2026.)
But subgroup matrix is different. This is a feature that directly unlocks the GPU's fixed-size matrix-multiply unit — tensor cores, in NVIDIA terms — and LLM prefill wants exactly that unit. The paper's kernel library already includes an sg_mat kernel that uses it. The problem is this one sentence.
The subgroup matrix feature isn't yet available in stable browser releases, but it is available natively in some WebGPU implementations.
Checking confirms this. On Chrome Platform Status, the subgroup-related entries are exactly Subgroups (134, enabled by default), subgroup_id (144), subgroup_uniformity (145), and Subgroup Size Control (slated for 152) — there is no subgroup matrix entry at all. Google is at the stage of saying it will bring a cooperative matrix proposal to the standards group, and as with subgroups, a large part of the discussion is expected to be about portability.
So the authors take a detour. Remember the earlier fact that Dawn is a C++ library — running the same backend code natively through Dawn lets you turn subgroup matrix on. Measure it that way, and the picture flips.
In fact, using LlamaWeb's native-run prefill numbers, LlamaWeb outperforms the other frameworks' prefill numbers on every device except WebLLM on the Apple M4 Pro, showing a geometric-mean speedup of 88% over WebLLM and 205% over Transformers.js.
Same code, same device, different execution environment. 49% of WebLLM inside the browser, 188% of WebLLM outside it. Today's browser-LLM prefill ceiling isn't algorithms or kernel quality — it's one un-standardized hardware feature.
The paper's conclusion strikes exactly that tone — the authors write that "while subgroup matrix isn't yet in browsers, once it reaches general availability, LlamaWeb is well-positioned to deliver competitive prefill performance." The load-bearing word is "once," not "is." Don't read past that conditional clause.
The Gap to Native — 2.5x to 10x
So what happens if we leave the browser and compare against native backends? The paper runs native backends on the same devices using llama.cpp's llama-bench for comparison.
As Fig. 5(a) shows, the CUDA and Vulkan backends still outpace WebGPU by up to 10x on prefill and 2.5x on decode, across both weight formats. This appears to be due to better utilization of tensor cores and access to vendor-specific advanced features such as asynchronous memory loads.
The Apple side is similar — "the Metal backend always outperforms the WebGPU backend, by more than 2x on prefill and 50% on decode."
This same gap shows up in the PR #17031 benchmark we saw earlier. It's preliminary numbers the PR author measured on their own M3 (that is, not a vendor report but a single author's single device, and llama-bench is llama.cpp's native CLI benchmarking tool).
Llama-3.2-1B-Instruct, Apple M3, llama-bench (measured by the PR #17031 author, preliminary numbers)
pp512 (t/s) tg128 (t/s)
F16 WebGPU 1014.17 ± 9.38 28.71 ± 0.19
F16 Metal 1368.47 ± 0.95 35.99 ± 0.78
Q4_0 WebGPU 960.52 ± 6.05 41.76 ± 0.62
Q4_0 Metal 1346.68 ± 1.21 103.92 ± 0.37
Look at Q4_0's decode — 41.76 versus 103.92. That's 2.5x. The exact same order-of-magnitude gap as the paper's numbers above reproduces independently here.
But the direction isn't only one way. This is the most surprising part of the paper — there really are cases where WebGPU beats vendor-specific backends.
- On Apple, vs. Vulkan: against the Vulkan backend running through MoltenVK, WebGPU is up to 59% faster on prefill and 45% faster on decode.
- On AMD: the native HIP backend always outperforms WebGPU, but the Vulkan backend trails WebGPU by 3x on prefill.
- On Intel: the WebGPU backend with safety checks turned off is the fastest on f16 model prefill, beating the SYCL backend by 23%, and it also beats native SYCL by 10% on decode for the same model.
The paper interprets this as an effect of cross-device tuning — even though WebGPU's underlying runtime on Linux is Vulkan itself, there are devices where LlamaWeb's tuned matrix-multiply parameters beat the Vulkan-specific backend. The intuition that "native is always faster" is wrong. More precisely: a well-tuned portable implementation beats a poorly-tuned native one.
Safety Isn't Free — The Bounds-Check Bill
In the browser, the WebGPU compiler inserts safety checks into shaders, to prevent out-of-bounds buffer access and division by zero. This isn't negotiable — you can't strip it out of a model where any arbitrary webpage can throw a shader at your GPU.
The paper measured what that costs.
Comparing performance with safety checks on versus off, prefill slows down by an average of 14% for q4_k_m models and 23% for f16 models, while decode slows down by 5% and 1% respectively. This slowdown reached as much as 42% on prefill for the NVIDIA RTX 5080.
In other words, safety checks eat double digits on prefill and are close to free on decode. This fits the same overall picture — a check gets attached to every index inside a compute-intensive matrix-multiply loop. And look again at the earlier Intel case: WebGPU beat SYCL under the condition of "safety checks turned off." That condition isn't available in the browser.
This shows up directly on the paper's list of future work — removing unnecessary bounds checks through better static analysis, handling floating-point differences more robustly at the language level to guarantee model stability under low-precision execution, and adding new data types like u16 to WebGPU to enable more efficient memory loading and native hardware acceleration.
Quantization Here Isn't a Speed Trick
This is a part that surprises you more the more experience you have with local LLMs.
As it currently stands, llama.cpp quantization on WebGPU is a technique for reducing the memory requirement needed to run a given model, not a performance optimization.
The reason q4 is used on CPU or CUDA is memory, sure, but also to save bandwidth and gain speed. On WebGPU, that equation doesn't hold. The paper gives two reasons. First, the dequantization routine for general-purpose weight formats is complex and compute-heavy — unlike hardware-native formats such as nvfp4, which WebGPU doesn't support. Second, the current layout may not be optimized for performance across the diverse range of GPUs WebGPU targets.
The M3 table above backs this up — Q4_0's prefill (960) is actually slower than F16's prefill (1014). Even though the file size dropped to a third, from 2.30GiB to 729.75MiB. On decode, quantization wins at 41.76 versus 28.71 (bandwidth dominates there), but on prefill, the dequantization cost eats the gain.
The practical implication is simple. In the browser, quantization is a means of "making a model that didn't run, run," not a means of "making a model that already ran, run faster." And given that, as we saw in the earlier section, the browser's real constraint is tab memory, that alone is worth plenty.
Browser Variance — Firefox Got Dropped Entirely
Leaving this paragraph out of a post about portability would be dishonest.
Firefox does support WebGPU, but we observed significantly lower performance. For example, on Apple M3, a llama model with q4_k_m weights ran at about 1 token per second on Firefox versus about 52 tokens per second on Chrome, so we exclude Firefox results from our evaluation.
That's 50x. The paper writes that Chrome, and Dawn underneath it, "consistently provided the highest and most stable WebGPU performance across many platforms," so unless otherwise noted, every experiment ran on Chrome. On iOS, no other browser engine is available, so Safari is used.
The sentence "WebGPU is supported by all three major browsers" is true, but leaning on it in production will hurt you. Feature support and performance parity are different axes, and as of July 2026, the latter is nowhere close to being in place.
There's a landmine on the floating-point side too. Look at one paper footnote — during development, using f16 accumulation in the register-tiling kernel caused some models like Qwen2.5 to produce gibberish output on Apple M-series GPUs, so that kernel currently uses f32 accumulation instead. In the authors' words, "differences in floating-point precision are a well-known problem, and handling them within WebGPU and its applications is not a solved problem." The nasty part of this class of bug is that it shows up not as a crash, but as silent quality degradation.
For Practitioners — Feature Detection and Fallback
At the code level, all of this boils down to one thing. Don't assume a feature — ask for it.
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) throw new Error('WebGPU 어댑터 없음');
// subgroups is enabled by default in Chrome 134+, but you still need
// to check that the adapter actually advertises it and request it explicitly.
const wanted = ['subgroups', 'shader-f16'].filter((f) => adapter.features.has(f));
const device = await adapter.requestDevice({ requiredFeatures: wanted });
// subgroup matrix is not in any stable browser as of July 2026.
// Don't write code that pretends it exists — treat the fallback path as the default path.
const hasSubgroups = device.features.has('subgroups');
LlamaWeb's design is exactly this shape — the sg_mat kernel exists, but it isn't the default path, and it falls back to register tiling on unsupported devices. To carry PR #17031's sentence over again, "on unsupported devices, the code falls back to the register tiling approach." In the browser, right now, that fallback is the path for every device.
One more thing. Kernel compilation incurs a one-time cost (roughly 1–5 seconds) during the model's first forward pass, after which it runs from a cached pipeline. Don't forget those few seconds when designing for first-token latency.
So, Should You Run an LLM in the Browser
Looking at the data, the decision criteria become fairly clear.
When it earns its cost
- Data must not leave the device. This is nearly the only overwhelming reason — it's the one kind of requirement worth accepting 2.5x slower for.
- The model is small. The models the paper actually measured range from 0.23GB to 3.11GB.
- Decode-dominant workload — short prompt, long generation. This is where browser inference loses the least.
- Deployment has to be a single URL. No install, no runtime, no server — that's still real value.
When it's not there yet
- Prefill-dominant — patterns like RAG where you feed in an entire long document. This is precisely where the gap widens to as much as 10x, and the feature that would fix it isn't in the standard yet.
- You need to support Firefox. 50x isn't a performance issue — it's a feature issue.
- You're sensitive to precision. The paper already documents a case where f16 accumulation quietly wrecked output.
- You can use a server GPU and have no privacy constraints. Then just run it on the server. Browser inference is a privacy and deployment technology, not a cost-saving one.
That last item matters most. "Zero server cost" is true, but in exchange you take on your users' battery, 2.5–10x the performance gap, and hardware variance across 8 vendors. That's not savings — it's a transfer.
Closing
To sum up the state of browser LLM inference in 2026 in one paragraph: the engineering has matured — static memory planning and OPFS streaming cut memory by 29–33%, and tuned portable kernels beat vendor-specific native backends on some devices. Yet on prefill, it still loses to native by up to 10x, and a large chunk of that reason is the absence of a standard feature that unlocks tensor cores. The proof is that running the same code outside the browser with that feature flips the ranking.
So the lesson of this story isn't "the browser is still slow." It's actually the opposite — the bottleneck now sits not in software but in the standardization timeline. Given that subgroups went through roughly a year of origin trial from the original proposal to being enabled by default in Chrome 134, it's clear what it means that cooperative matrix is still at the proposal stage. And the center of that discussion will be portability, not performance — covering 8 vendors' matrix units with one safe API is inherently hard.
Until then, deciding to run an LLM in the browser is a privacy decision, not a performance decision. Approach it with that frame, and this technology already earns its keep today. Approach it with a benchmark frame, and you'll be disappointed.
References
- Llamas on the Web: Memory-Efficient, Performance-Portable, and Multi-Precision LLM Inference with WebGPU (Levine et al., arXiv:2605.20706, May 20, 2026)
- ggml webgpu: faster matrix multiplication/matrix-vector multiplication — llama.cpp PR #17031 (merged November 8, 2025)
- WebGPU Subgroups — Chrome Platform Status (enabled by default in Chrome 134)
- WebGPU
subgroup_idfeature — Chrome Platform Status (Chrome 144) - What's New in WebGPU — Chrome for Developers release notes index
- WebAssembly and WebGPU enhancements for faster Web AI, part 2 — background on the cooperative matrix proposal
- Real GPU Compute in the Browser — A Hands-On Guide to WebGPU Compute Shaders and WGSL in 2026 (related post)