브라우저에서 LLM 실행하기: WebGPU는 초당 63 토큰, WASM은 1.6
요약
Transformers.js와 WebGPU를 활용하여 LLM을 웹 브라우저에서 직접 실행하는 방법을 소개합니다. 이를 통해 추론 과정에 대한 서버 비용 없이 사용자 기기에서 AI 모델을 구동할 수 있습니다. 테스트 결과, 360M 파라미터 모델이 WebGPU 환경에서 초당 63 토큰의 높은 성능을 보였습니다.
핵심 포인트
- WebGPU와 Transformers.js를 이용해 브라우저 내 LLM 실행 가능
- 추론(inference) 비용 없이 사용자 기기에서 AI 구동 아키텍처 구현
- WebGPU 사용 시 WASM 폴백 대비 월등한 속도 (63 토큰/초 vs 1.6 토큰/초)
- 사용자 기기에 모델을 다운로드하여 로컬로 실행하는 트레이드오프 존재
원래 tensorust.com에 게시됨.
요약: Transformers.js 모듈, Web Worker, 그리고 WebGPU를 사용하여 작은 언어 모델을 웹 페이지 내에서 완전히 실행할 수 있습니다. 저희는 BEINGS라는 채팅방에서 이를 구현했는데, 각 사용자의 AI 크리처가 해당 사용자 자신의 노트북에서 생각하기 때문에 플랫폼은 추론(inference)에 대해 $0를 지불합니다. 2026년 10월 17일에 최신 측정치를 확인했습니다: 동일한 360M 파라미터 모델이 WebGPU에서는 초당 63 토큰, WASM 폴백(fallback)에서는 1.6 토큰을 작성했으며, 1B Llama은 반 네 시간 이내에 답변했고, 0.5B Qwen은 너무 많은 헛소리를 생성하여 높은 온도(temperature)로 설정된 응답 중 절반을 버려야 했습니다.
먼저 간단히 말씀드리자면, LLM을 브라우저에서 실행하려면 세 가지가 필요합니다: ONNX에 이미 변환되고 양자화된(4비트 가중치로, 1B 모델의 경우 5GB 대신 약 1.2GB) 모델, 페이지에서 이를 실행하는 런타임(Transformers.js는 ONNX Runtime Web을 감쌈), 그리고 폴백으로 CPU WebAssembly를 가진 GPU 백엔드(WebGPU)입니다. 모델을 Web Worker에 넣어 페이지가 절대 멈추지 않도록 합니다. 이는 약 90줄의 JavaScript로, 아래는 저희가 배포하는 간략하게 다듬은 파일입니다.
더 어려운 부분은
BEINGS는 사람들이 각자의 AI 생명체(creatures)를 가져와서 만나는 구식 채팅방입니다. 재밌는 건 이런 것들이 서로 대화하고 사람들과도 하루 종일, 프롬프트 없이 말을 나눈다는 점이죠. 무료 장난감치고는 모델 호출이 엄청나게 많이 필요한 구조예요.
만약 이 호출들이 우리의 서버를 거쳐 유료 API로 전송된다면, 활발한 방 하나하나가 청구서가 될 겁니다. 그래서 아키텍처의 핵심은 정반대였습니다: 각 생명체는 그것을 가져온 사람의 기기에서 실행됩니다. 말 그대로 사용자의 노트북이 사용자들의 봇들을 구동하는 거죠. 랜딩 페이지에 명확히 나와 있습니다: API 키도, 가입도, 지불할 것도 없습니다. 클라우드 기반 두뇌(OpenAI, Claude, Gemini, OpenRouter)는 선택적 업그레이드이며, 이때 사용자가 자신의 키를 붙여넣으면 그 키가 브라우저의 localStorage에 저장되어 제공업체로 바로 전송되고, 우리를 거치지 않습니다.
여기에는 솔직한 트레이드오프(trade-offs)가 있습니다. 사용자는 모델을 한 번 다운로드합니다 (수백 MB에서 1GB 이상). 그들의 GPU가 작업을 수행하며, 작은 모델은 호스팅되는 큰 모델들보다 훨씬 지능적이지 못합니다. 혼란스러운 채팅방의 목적이라면, '멍청하지만 무료'인 것이 적절한 거래입니다. 하지만 고객 지원 봇의 목적이라면, 아마 그렇지 않을 겁니다.

캡처된 BEINGS 라이브 랜딩 페이지 (2026년 10월 17일, 동부 표준시 오전 2:37). 푸터: 생명체들은 WebGPU를 사용하여 사용자의 기기에서 생각하며, CPU로 폴백(falling back)합니다.
Step 1: 모델을 담는 워커 (the worker that holds the model)
생성 과정은 무겁고 동기적인 숫자 계산입니다. 이를 메인 스레드(main thread)에서 실행하면 모델이 생각하는 동안 페이지가 멈춥니다. 그래서 모델은 Web Worker에 존재합니다: 트랜스포머스.js(Transformers.js)를 로드하고, 하나의 모델을 보유하며, 메시지를 통해 페이지와 통신하는 별도의 스레드입니다. 여기 파일이 있습니다. 오류 분기만 잘라낸 버전입니다 (public/llm-worker.js):
import {
pipeline,
env,
...
다음 네 가지 세부 사항이 중요하게 다루어집니다:
dtype은 장치에 따라 변경됩니다. WebGPU에서는q4f16(4비트 가중치, 16비트 연산)을 로드하고, WASM에서는q4를 로드합니다. 이들은 Hugging Face에서 서로 다른 파일이며, dtypes 가이드에 각 모델이 어떤 dtype으로 배포되는지 나열되어 있습니다. SmolLM2의 경우 WebGPU 파일은 272.7 MB이고 WASM 파일은 387.9 MB입니다.- WebGPU가 존재하더라도 실패할 수 있습니다.
"gpu" in navigator가 참이라고 해서 드라이버가 협조한다는 의미는 아니므로, WebGPU 로드에 실패하면 오류를 표시하는 대신 WASM으로 재시도합니다. - 진행률이 페이지로 돌아갑니다. 진행률 표시줄 없이 1.2 GB의 첫 다운로드는 마치 사이트가 고장 난 것처럼 보입니다.
- Transformers.js는 npm이 아닌, 고정된 CDN URL에서 가져옵니다. jsDelivr의 버전 4.2.0을 사용합니다. 이는 의도적인 것이며, 다음 섹션에서 다룰 내용입니다.
번들러 트랩: 워커가 번들링되지 않는 이유
현대 프레임워크에서 워커를 작성하는 '정석적인' 방법은 TypeScript 파일과 new Worker(new URL("./llm.worker.ts", import.meta.url))을 사용하고, 번들러에게 처리하도록 맡기는 것입니다. BEINGS는 Turbopack으로 구축된 Next.js 16 앱입니다. 우리가 이 방식으로 작업했을 때, Turbopack은 워커를 .next/static/media/ 내의 순수 .ts 파일로 배포했습니다. 어떤 브라우저도 TypeScript를 실행할 수 없기 때문에 워커가 시작되지 않았고, 단 하나의 빌드 오류 없이 모든 로컬 추론이 작동하지 않게 되었습니다.
해결책은 지루하고 영구적입니다: 워커는 /public에 있는 수동으로 작성된 JavaScript 파일이며, 그대로 서비스되고, 일반 경로로 로드되며, 번들러가 건드리지 못하도록 CDN에서 Transformers.js를 가져옵니다. 이 파일 자체의 헤더 주석에도 그렇게 명시되어 있으며, 프로젝트 노트에는 이를 다시 번들에 "정리(tidy)"하지 말라고 되어 있습니다. 페이지 측 코드는 다음과 같습니다 (lib/brains/client.ts):
// Served from /public as a real ES module; see the comment in that file.
const w = new Worker("/llm-worker.js", { type: "module" });
this.worker = w;
...
Vite나 webpack을 사용한다면, 워커 번들링이 잘 작동할 것입니다. 전송되는 교훈은 더 작습니다. 빌드가 완료된 후 배포된 사이트를 열고 워커가 실제로 시작하는지 확인하세요. 조용히 죽어버린 워커는 느린 모델처럼 보입니다.
2단계: 한 모델로, 한 번에 하나의 생성만
사용자는 여섯 마리의 생물을 한 방으로 데려올 수 있습니다. 만약 이 여섯 마리가 모두 동시에 모델을 호출한다면, 노트북은 여섯 개의 복사본을 로딩하거나 하나의 GPU를 두고 싸우게 될 것입니다. 따라서 페이지 측(page side)은 정확히 하나의 워커만 소유하며 모든 생성 요청을 마지막 것 뒤에 큐잉합니다 (lib/brains/client.ts):
/** Serialised: generations run one after another, never concurrently. */
generate(messages: ChatTurn[], maxTokens = 60, temperature = 0.75): Promise<string> {
const run = () =>
...
this.queue.then(run, run)이 핵심 트릭입니다. 각 호출은 이전 요청이 성공하든 실패하든 정착할 때까지 기다리기 때문에, 하나의 충돌한 생성 요청이 라인을 막지 못합니다. 90초 타임아웃이 존재하는 이유는 WASM 폴백(fallback)의 경우 단일 답변에 상당한 시간이 걸릴 수 있기 때문이며, 이는 다음 섹션에서 보여주듯이 그렇습니다.
브라우저에서 LLM은 실제로 얼마나 빠를까?
프로젝트 노트에는 불편한 문장이 하나 있었습니다. 인-브라우저 WebGPU 추론(inference)이 실제 하드웨어에서 검증된 적이 없다는 것이었습니다. 왜냐하면 빌드 세션에 브라우저가 없었기 때문입니다. 그래서 오늘 밤 저희가 측정해 보았습니다. 설정: Windows 11의 Chrome 155, RTX 4070 데스크톱(WebGPU는 어댑터를 nvidia / lovelace로 보고함), 24개의 논리적 CPU 코어. 작은 스크립트가 라이브 사이트에 자체 탭을 열고 실제 /llm-worker.js를 시작하고, 각 모델을 로드한 다음, 앱의 Braxton 금융계 브로 페르소나와 세 가지 다른 방 메시지, 각각 네 번씩, 두 개의 온도 설정으로 총 24번의 실제 채팅 요청을 보냈습니다. 앱에서 사용하는 것과 동일하게 70 토큰 제한이었습니다.

Chrome 155, RTX 4070, Oct 17 2026, 2:19 to 2:34 AM ET 기준, 12개 응답에 대한 중앙값 토큰/초.
제목은 모델이 아니라 백엔드입니다. SmolLM2 360M은 WebGPU에서 초당 62.9 토큰, WASM에서는 1.6 토큰을 기록했습니다 (중앙값, temperature 0.75). 중앙값 응답 시간은 GPU에서 0.54초였고 CPU 경로는 26.9초가 걸렸습니다. 채팅방 환경에서 27초는 영겁의 시간입니다. 당신의 모델이 문장을 끝내기도 전에 대화는 이미 다음 주제로 넘어갔을 것입니다.
그 격차 중 일부는 아마도 스스로 만든 것일 수 있으며, 직접 사이트에서 확인해 볼 가치가 있습니다. ONNX Runtime Web은 페이지가 cross-origin isolated 상태일 때만 여러 CPU 스레드를 사용하며, 그렇지 않으면 단일 스레드로 실행됩니다 (이는 env flags docs에서 가져온 내용입니다). 저희 페이지는 Cross-Origin-Opener-Policy: same-origin을 전송하지만, Cross-Origin-Embedder-Policy는 보내지 않았고, 벤치마크 탭에서는 crossOriginIsolated: false이며 SharedArrayBuffer가 없다고 보고되었습니다. 따라서 저희의 WASM 폴백은 24개 스레드 기계의 단일 스레드에서 실행된 것입니다. 아직 격리(isolation)를 켜고 측정해 보지는 않았기 때문에 정확한 수치를 약속할 수는 없지만, 제 예상으로는 폴백이 몇 배는 빨라질 것이라고 생각합니다. 문제는 COEP가 옵트인하지 않은 크로스-오리진 임베드를 차단한다는 것이고, BEINGS는 YouTube와 Wikimedia 미디어를 임베드합니다. web.dev의 COOP 및 COEP 가이드에서 관련 옵션을 다루고 있습니다.
또 다른 놀라운 점은 다음과 같습니다. 가장 큰 모델이 가장 빠르게 응답했다는 것입니다. Llama 3.2 1B는 초당 평균 5564 토큰으로, SmolLM2와 근접했지만, 중간값(median) 응답 시간은 약 0.40.5초에 달했습니다. 이는 해당 모델이 필요한 내용을 약 2430토큰에서 말하고 멈췄기 때문입니다. 반면, 더 작은 모델들은 70토큰 제한까지 장황하게 답변했습니다. Qwen 2.5 0.5B는 세 모델 중 가장 느린 성능을 보여 초당 3740토큰에 불과했고, 응답당 약 1.8초가 걸렸습니다.

콜드 로드(Cold loads)는 모델 파일을 새로 다운로드했습니다 (Cache Storage 항목 날짜: 2026년 10월 17일); 웜 로드(warm loads)는 몇 분 후에 브라우저 캐시에서 읽어왔습니다.
실제 첫 실행 비용은 다운로드 과정입니다
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기