브라우저에서 AI를 돌릴 수 있다고요?
2026-05-26"AI를 서버 없이 돌린다고요?"
사이드 프로젝트를 만들다가 AI 리뷰 기능을 붙이고 싶어졌어요. 변환된 코드를 보고 "여기 모바일에서 깨질 수 있어요" 같은 한국어 코멘트를 자동으로 달아주는 기능이요. 자연스럽게 OpenAI나 Anthropic API를 떠올렸죠.
그런데 사이드 프로젝트이니 최대한 비용이 안 나가는 방법을 찾고 싶었어요.
대안을 찾다가 Transformers.js라는 라이브러리를 발견했습니다. 처음 봤을 때 한 줄 설명이 브라우저에서 Hugging Face 모델을 서버 없이 실행이었어요. 이게 무슨 소리야 싶었죠. AI 모델을 브라우저가 어떻게 돌려?
처음 데모를 직접 띄워봤을 때 신기하게도 350MB짜리 모델을 사용자 브라우저에 한 번 다운받아 두면, 그 다음부터는 인터넷도 안 끊기면 서버 없이 돈다는 거예요.
AI 모델이란 ?
여기서 한번 AI 모델을 간략하게 설명하고 넘어가겠습니다.
LLM(Large Language Model, 우리가 흔히 말하는 AI)을 프로그램이라고 생각하시면 헷갈려요. 이건 엄청나게 큰 숫자 묶음이에요. 수십억 개의 숫자(가중치라고 불러요)가 일정한 구조로 배치된 거예요.
이 숫자 묶음에 "안녕하세요" 같은 입력을 흘려보내면, 일련의 계산이 일어나서 "반갑습니다" 같은 출력이 나와요. 프로그램이 아니라 계산기에 가까운 거죠.
그래서 AI 모델 파일은 보통 수백 MB ~ 수십 GB예요. 코드가 아니라 학습된 숫자들이거든요.
이 사실이 중요한 이유는, 누가 돌려도 결과가 같다는 거예요. OpenAI 서버가 돌리든, 내 노트북이 돌리든, 같은 모델에 같은 입력을 넣으면 같은 출력이 나와요.
왜 보통 AI를 서버에서 돌릴까
이유는 단순해요. 그 수십억 개의 숫자 계산이 무거우니까요.
GPT-4 같은 거대 모델은 수천억 개의 숫자를 갖고 있고, 답변 한 번 만들려면 GPU 여러 장이 협력해야 해요. 내 노트북에서는 돌릴 수가 없어요. 그래서 OpenAI가 자기네 데이터센터에서 돌리고, 우리는 API로 결과만 받아오죠.
근데 모델이 작으면 사정이 달라져요. 5억 개(0.5B) 정도 되는 작은 모델은 요즘 노트북·핸드폰에서도 돌아가요. 답변 품질이 GPT-4만큼은 아니지만, 특정 작업에는 충분히 쓸 만한 수준이에요.
이런 작은 모델들을 SLM(Small Language Model)이라고 불러요. 그리고 이런 SLM을 브라우저에서 돌리게 해주는 도구가 Transformers.js예요.
Transformers.js의 정체
원래 Hugging Face라는 회사가 파이썬용 transformers라는 라이브러리를 만들었어요. AI 모델을 쉽게 가져다 쓸 수 있게 해주는 도구인데, 사실상 업계 표준이에요. 모델 하나 쓰려면 코드 세 줄이면 끝나요.
Transformers.js는 같은 걸 자바스크립트로 옮긴 거예요. 파이썬 코드랑 거의 똑같은 API로 브라우저에서 모델을 돌릴 수 있어요. 파이썬 코드는 이렇게 생겼는데:
from transformers import pipeline
generator = pipeline("text-generation", "Qwen/Qwen2.5-0.5B-Instruct")
result = generator("안녕하세요")
자바스크립트도 거의 똑같아요:
import { pipeline } from "@huggingface/transformers";
const generator = await pipeline(
"text-generation",
"Qwen/Qwen2.5-0.5B-Instruct",
);
const result = await generator("안녕하세요");
이게 파이썬으로 익숙한 사람이 자바스크립트로 넘어갈 때도 좋고, 모델 비교를 같은 코드로 양쪽에서 할 수 있다는 점도 좋아요.
모델 파일은 어디서 오는가
신기한 부분이에요. pipeline("text-generation", "Qwen/Qwen2.5-0.5B-Instruct") 이 코드를 실행하면 그 순간 모델 파일이 어디선가 다운받아져요. 어디서 올까요?
Hugging Face Hub라는 곳에서요. 깃허브 같은 건데, 코드가 아니라 AI 모델을 호스팅하는 사이트예요. 누구나 모델을 올릴 수 있고, 다운받을 수 있어요. 무료고요.
다운받은 모델은 브라우저 IndexedDB(브라우저 안의 작은 데이터베이스)에 저장돼요. 첫 방문 때만 350MB를 받고, 그 다음부터는 즉시 로드예요.
저는 이 동작을 처음 봤을 때 npm install 같은 느낌이라고 생각했어요. 라이브러리를 한 번 받아두면 그 다음부터는 import만으로 쓸 수 있는 것처럼, 모델도 한 번 받아두면 그 다음부터는 즉시 쓸 수 있는 거죠. 다른 점은 npm처럼 미리 받는 게 아니라, 실행 시점에 받는다는 거예요.
WebGPU — 이게 진짜 돌아가는 이유
여기서 한 가지 의문이 들 거예요. 노트북에서 돌릴 정도로 작은 모델이라고 해도, 5억 개 숫자 계산은 여전히 무거워요. 자바스크립트가 그걸 어떻게 빠르게 처리하죠?
여기서 등장하는 게 WebGPU예요.
WebGPU는 브라우저가 GPU(그래픽카드)를 직접 쓰게 해주는 표준이에요. 원래 브라우저는 안전을 위해 시스템 자원에 직접 접근 못 하게 막혀 있는데, GPU만큼은 예외적으로 쓸 수 있게 열어준 거예요. 게임이나 3D 그래픽 때문에 시작된 표준인데, GPU가 잘하는 게 결국 대량의 숫자 계산이라 AI랑 궁합이 좋아요.
Transformers.js v3에 WebGPU 지원이 들어왔는데, WebGPU 지원 덕분에 모델 로딩 시 device: 'webgpu' 옵션 하나로 GPU 가속을 켤 수 있고, WASM(CPU 실행) 대비 최대 100배까지 빨라질 수 있어요.
이게 작은 모델이 브라우저에서 실용적인 속도로 동작하는 결정적 이유예요. GPU 없이 CPU만으로 돌리면 답변 한 번에 30초씩 걸리지만, WebGPU 켜면 1~2초로 떨어져요.
실제로 어떻게 쓰는가
설치
npm install @huggingface/transformers
이게 전부예요. 모델 파일은 아직 안 받아요. 라이브러리만 받아져요.
가장 간단한 실행
import { pipeline } from "@huggingface/transformers";
// 1. 모델 로드 (첫 호출 시 다운로드 시작)
const generator = await pipeline(
"text-generation",
"Qwen/Qwen2.5-0.5B-Instruct",
{ device: "webgpu", dtype: "q4" },
);
// 2. 추론
const output = await generator(
"변환된 코드를 보고 한국어로 한 줄 코멘트:\n" + convertedCode,
{ max_new_tokens: 100 },
);
console.log(output[0].generated_text);
여기 두 가지 옵션이 핵심이에요.
device: "webgpu"— GPU 가속. 없으면 자동으로 CPU(WASM)로 떨어져요.dtype: "q4"— 양자화(quantization)예요. 모델 파일의 숫자 정밀도를 낮춰서 크기를 줄이고 속도를 올리는 기법. q4면 4비트, q8이면 8비트. 정밀도가 낮을수록 가볍지만 답변 품질이 살짝 떨어져요. 작은 모델일수록 q8이 안전하고, 큰 모델은 q4도 충분할 때가 많아요.
모델 다운로드 진행률 보여주기
위 코드의 문제는 첫 실행 때 사용자가 350MB를 기다린다는 거예요. 이걸 해결하려면 진행률 콜백을 받아야 해요.
const generator = await pipeline(
"text-generation",
"Qwen/Qwen2.5-0.5B-Instruct",
{
device: "webgpu",
dtype: "q4",
progress_callback: (progress) => {
// status: 'initiate' | 'download' | 'progress' | 'done' | 'ready'
// file: 다운로드 중인 파일명 (모델은 보통 여러 파일로 나뉘어요)
// progress: 0 ~ 100
console.log(progress.status, progress.file, progress.progress);
},
},
);
이걸 UI 상태에 연결해서 진행률 바로 보여주면 사용자가 멍하니 기다리지 않아요. "어디서 멈춰 있나"가 아니라 "지금 X% 받는 중이구나"로 인식이 바뀌어요.
WebGPU 없는 환경 대비
WebGPU는 2026년 기준으로도 모든 브라우저에서 다 되지는 않아요. Safari는 최근에야 켜졌고, 모바일 브라우저는 더 늦어요. 그래서 WebGPU가 안 되면 자동으로 WASM(CPU)으로 떨어지는 코드가 필요합니다.
async function loadModel(modelName: string) {
try {
// 먼저 WebGPU 시도
return await pipeline("text-generation", modelName, {
device: "webgpu",
dtype: "q4",
});
} catch (e) {
// 실패하면 WASM (CPU)
console.warn("WebGPU 안 됨, CPU로 떨어집니다", e);
return await pipeline("text-generation", modelName, {
device: "wasm",
});
}
}
Web Worker로 분리 — 사실상 필수
여기까지 봤으면 코드가 잘 돌아가긴 할 거예요. 근데 한 가지 더 짚고 가야 해요.
위 코드를 그냥 React 컴포넌트에 박으면 모델 다운로드 350MB 동안 화면이 얼어붙어요. 클릭도 안 먹고, 스크롤도 안 되고, 탭이 죽은 줄 알게 돼요. 자바스크립트는 한 번에 한 가지 일만 하니까요.
이걸 해결하려면 Web Worker라는 걸 써야 해요. 별도 스레드에서 모델을 돌리고, 메인 스레드는 UI만 담당하는 구조예요.
// worker.ts (백그라운드에서 도는 코드)
import { pipeline } from "@huggingface/transformers";
let generator = null;
self.addEventListener("message", async (event) => {
const { type, payload } = event.data;
if (type === "LOAD") {
generator = await pipeline("text-generation", payload.modelName, {
device: "webgpu",
dtype: "q4",
progress_callback: (p) =>
self.postMessage({ type: "PROGRESS", payload: p }),
});
self.postMessage({ type: "READY" });
}
if (type === "GENERATE") {
const output = await generator(payload.prompt, { max_new_tokens: 200 });
self.postMessage({ type: "RESULT", payload: output });
}
});
// 메인 컴포넌트
const worker = new Worker(new URL("./worker.ts", import.meta.url), {
type: "module",
});
worker.addEventListener("message", (event) => {
const { type, payload } = event.data;
if (type === "PROGRESS") setProgress(payload.progress);
if (type === "READY") setReady(true);
if (type === "RESULT") setReview(payload[0].generated_text);
});
// 사용
worker.postMessage({
type: "LOAD",
payload: { modelName: "Qwen/Qwen2.5-0.5B-Instruct" },
});
worker.postMessage({
type: "GENERATE",
payload: { prompt: "변환된 코드: ..." },
});
코드가 좀 늘어났지만, 이렇게 분리하면 모델 다운로드 중에도 UI가 살아 있어요. 진행률 바도 부드럽게 움직이고, 사용자가 다른 동작도 할 수 있어요. 무거운 일을 다루는 라이브러리를 쓸 때는 거의 패턴이에요.
모델은 어디서 고르나
마지막으로 어떤 모델을 쓰느냐가 결과 품질을 좌우해요. Hugging Face Hub에서 Transformers.js 호환 모델만 따로 모아둔 페이지가 있어요. ONNX 형식으로 변환된 것만 돌아가거든요.
huggingface.co/models?library=transformers.js 이 페이지에서 필터 걸어서 찾을 수 있어요.
선택할 때 고려할 점:
- 모델 크기 — 0.5B(약 350MB) / 1B(약 700MB) / 1.5B(약 1GB) 정도가 브라우저에서 실용적인 상한이에요. 그 이상은 모바일에서 메모리 부족으로 죽어요.
- 한국어 지원 — 모델 카드(README)에 학습 데이터 언어가 적혀 있어요. 한국어가 명시된 모델을 골라야 한국어 출력이 자연스러워요. 영어만 학습한 모델은 한국어로 추론하면 횡설수설해요.
- Instruction-tuned인가 — 모델 이름 끝에
-Instruct나-Chat이 붙은 게 대화·지시 따르기에 맞춰 학습된 거예요. 베이스 모델은 문장 이어쓰기만 하니까 우리가 원하는 답변을 안 줘요.
이 세 가지 기준으로 보면 2026년 기준 Qwen2.5 시리즈나 Gemma3 시리즈가 무난해요. 둘 다 한국어가 되고, 0.5B ~ 3B 크기로 여러 옵션이 있어요.
정리하면
처음에는 AI는 OpenAI나 Anthropic API로 부르는 것이라고 생각했습니다. 근데 사이드 프로젝트 하면서 알게 된 건, 프로젝트 성격에 따라 SLM을 직접 돌리는 게 더 맞는 경우가 있다는 거예요.
특히:
- 코드를 외부로 보내면 안 되는 도구
- 오프라인에서도 돌아야 하는 도구
- 호출 비용을 0으로 만들고 싶은 도구
- 프라이버시가 핵심 가치인 도구
이런 경우엔 Transformers.js사용하는걸 추천드립니다.
참고 자료
- Transformers.js 공식 문서 — API와 가이드가 잘 정리돼 있어요
- Hugging Face Hub — Transformers.js 호환 모델 — 모델 골라보기
- WebGPU caniuse — 어떤 브라우저에서 되는지 한눈에