Back

브라우저에서 LLM 돌리기: WebGPU, Transformers.js, Chrome 내장 AI 총정리

지금 만드는 AI 기능은 전부 API 콜이에요. 유저가 프롬프트 치면, 서버가 OpenAI한테 전달하고, 800ms-3초 기다리고, $0.01-0.50 내고, 응답 돌려주는 구조죠. DAU 1만인 채팅 기능이면 API 비용만 한 달에 $3,000-15,000 — 서버 인프라 빼고요.

근데 모델이 유저 기기에서 돌아간다면? API 콜 없음. 연산 시간 외 레이턴시 없음. 추론 비용 없음. 유저 데이터가 브라우저 밖으로 나갈 일도 없음.

더 이상 가정이 아니에요. 2026년, 브라우저는 진짜 AI 추론 런타임이 됐어요. WebGPU가 네이티브에 가까운 GPU 접근을 제공하고, 2GB 이하의 양자화 모델이 일반 하드웨어에서 대화형 속도로 돌아가요. Chrome은 Gemini Nano를 기기 내장으로 제공하고요. Transformers.js 같은 라이브러리 덕에 개발 경험도 놀라울 정도로 매끄러워요.

이 글에서는 브라우저에서 LLM을 돌리는 데 필요한 모든 걸 다뤄요. 기술 스택, 모델 선택과 양자화, 실제 하드웨어 벤치마크, Chrome 내장 AI API, 그리고 클라이언트 사이드 AI 기능을 프로덕션에 올리는 패턴까지. TypeScript 코드도 전부 포함이에요.

왜 브라우저에서 AI를 돌려야 할까?

구현에 들어가기 전에, 클라이언트 사이드 AI가 맞는 상황과 아닌 상황을 확실히 짚고 갈게요.

브라우저 AI가 좋은 이유

추론당 추가 비용 제로. 모델 다운로드 한 번 하면, 이후 모든 추론은 공짜예요. 유저당 쿼리가 많은 기능(자동완성, 문법 교정, 코드 제안)에서 단위 경제학이 API 콜 대비 압도적이에요.

설계상 프라이버시. 유저 데이터가 기기를 떠나지 않아요. 개인정보 정책 곡예도 필요 없고, GDPR 데이터 전송 이슈도 없고, 학습 데이터 유출 위험도 없어요. 민감한 분야(의료, 법률, 개인 일기)에서는 있으면 좋은 게 아니라 필수예요.

100ms 이하 레이턴시. 네트워크 라운드트립이 없으니 작은 모델에서 거의 즉각적인 응답이 가능해요. 자동완성, 인라인 제안, 실시간 분류가 찰나에 느껴져요.

오프라인. 모델이 캐시되면 네트워크 없이도 동작해요. 비행기에서도 되는 AI PWA — 확실한 차별점이죠.

Rate limit 없음. API 쿼터도, 스로틀링도, HN에 올라간 새벽 3시에 "429 Too Many Requests"도 없어요.

서버 사이드 AI가 여전히 나은 경우

대형 모델 능력. GPT-4급 추론은 아직 100B+ 파라미터 모델이 필요한데, 브라우저 탭에 넣을 순 없어요. 복잡한 추론, 멀티스텝 에이전트, 긴 컨텍스트 윈도우에는 API가 아직 필수예요.

첫 로딩 경험. 모델 다운로드(500MB-2GB)가 처음 사용 시 큰 대기를 만들어요. 느린 연결의 유저는 첫 추론까지 몇 분을 기다릴 수 있어요.

모바일 배터리. 모바일에서 GPU 추론은 배터리를 공격적으로 소모해요. 무거운 추론은 모바일에서 서버 처리가 맞아요.

일관성 보장. GPU, 드라이버, 양자화가 다르면 출력도 미세하게 달라요. 재현 가능한 결과가 필요하면 서버 추론이 제어하기 편해요.

2026년 최적 전략: 고빈도·저복잡도 작업에 브라우저 AI(자동완성, 분류, 짧은 요약, 임베딩), 무거운 추론에 서버 AI(에이전트, 장문 생성, 복잡 분석).

기술 스택

2026년 브라우저 AI를 가능케 하는 세 축:

1. WebGPU — 성능의 뼈대

WebGPU는 WebGL을 대체하는 최신 GPU API예요. 그래픽용으로 설계된 WebGL과 달리, WebGPU는 컴퓨트 워크로드를 위해 만들어졌어요 — 정확히 뉴럴 네트워크 추론에 필요한 거죠.

// WebGPU 지원 체크 async function checkWebGPU(): Promise<GPUDevice | null> { if (!navigator.gpu) { console.warn('이 브라우저에서 WebGPU를 지원하지 않습니다'); return null; } const adapter = await navigator.gpu.requestAdapter(); if (!adapter) { console.warn('GPU 어댑터를 찾을 수 없습니다'); return null; } const device = await adapter.requestDevice(); // GPU 정보 출력 const info = await adapter.requestAdapterInfo(); console.log(`GPU: ${info.vendor} ${info.device}`); console.log(`최대 버퍼 크기: ${device.limits.maxBufferSize / 1024 / 1024}MB`); console.log(`최대 컴퓨트 워크그룹 크기: ${device.limits.maxComputeWorkgroupSizeX}`); return device; }

브라우저 지원 현황 (2026년 3월):

브라우저WebGPU 상태비고
Chrome 113+✅ 안정2023년 4월부터 정식 지원
Edge 113+✅ 안정Chromium 기반, Chrome과 동일
Firefox 147+✅ 안정2026년 1월부터 기본 활성화 (Win/macOS)
Safari 26+✅ 안정macOS/iOS/iPadOS 전체 WebGPU 지원
모바일 Chrome⚠️ Android만플래그십 GPU 필요 (Adreno 730+)
iOS Safari 26+✅ 지원iOS 26+에서 WebGPU 사용 가능

행렬 곱셈 WebGPU vs WebGL 성능 (트랜스포머의 핵심 연산):

연산WebGLWebGPU속도 향상
MatMul 1024×102445ms8ms5.6배
MatMul 4096×4096890ms95ms9.4배
배치 어텐션 (8 헤드)120ms18ms6.7배
전체 순전파 (125M 파라미터)340ms52ms6.5배

차이가 거대해요. WebGPU의 컴퓨트 셰이더, 공유 메모리, 워크그룹 동기화가 브라우저 내 실시간 LLM 추론을 현실로 만들어요.

2. Transformers.js — 개발자 친화적 경로

Transformers.js(Hugging Face)는 익숙한 Python Transformers API를 JavaScript로 가져와요. 내부적으로 ONNX Runtime Web을 사용하고, WebGPU로 가속해요.

import { pipeline, env } from '@huggingface/transformers'; // 브라우저 환경 설정 env.allowLocalModels = false; env.useBrowserCache = true; // 텍스트 생성 — 완전히 클라이언트 사이드에서 실행 const generator = await pipeline('text-generation', 'onnx-community/Qwen2.5-0.5B-Instruct', { device: 'webgpu', dtype: 'q4', // 4비트 양자화 } ); const output = await generator('WebGPU를 한 문단으로 설명해줘:', { max_new_tokens: 150, temperature: 0.7, do_sample: true, }); console.log(output[0].generated_text);

Transformers.js v3 핵심 기능:

  • WebGPU 디바이스 타겟 (device: 'webgpu')
  • 내장 양자화 (dtype: 'q4', 'q4f16', 'fp16')
  • 채팅 UI용 스트리밍 토큰 생성
  • Hugging Face에 1,200개 이상의 사전 변환 ONNX 모델
  • 브라우저 Cache API에 모델 캐싱 (세션 간 유지)
  • Web Worker로 논블로킹 추론

3. ONNX Runtime Web — 추론 엔진

ONNX Runtime Web은 Transformers.js 밑에 깔린 엔진이에요. 더 로우레벨 제어가 필요하거나 커스텀 ONNX 모델이 있으면 직접 쓸 수 있어요:

import * as ort from 'onnxruntime-web/webgpu'; async function runInference(modelPath: string, inputText: string) { // WebGPU 실행 프로바이더로 세션 생성 const session = await ort.InferenceSession.create(modelPath, { executionProviders: ['webgpu'], graphOptimizationLevel: 'all', }); // 입력 텐서 준비 const inputIds = tokenize(inputText); // 토크나이저 const tensor = new ort.Tensor('int64', BigInt64Array.from(inputIds.map(BigInt)), [1, inputIds.length] ); // 추론 실행 const results = await session.run({ input_ids: tensor }); return results.logits; }

ONNX Runtime 직접 사용 vs Transformers.js 비교:

상황Transformers.jsONNX Runtime 직접
표준 NLP 작업✅ 하이레벨 API오버킬
커스텀 파인튜닝 모델ONNX 변환 필요✅ 완전한 제어
텍스트 외 모달리티 (오디오, 비전)✅ 지원 파이프라인커스텀 파이프라인용
최대 성능 튜닝제한적 제어✅ 세션 옵션, 그래프 최적화
프로토타입 속도✅ 3줄 코드보일러플레이트 더 많음

모델 선택: 브라우저에서 뭐가 돌아갈까?

핵심 질문이에요. fp16 70B 파라미터 모델은 VRAM 140GB가 필요하니까 — 브라우저 탭에선 당연히 안 돼요. 하지만 공격적인 양자화를 적용하면 생각보다 선택지가 많아요.

잘 돌아가는 모델 (2026년 3월 기준)

모델파라미터양자화 크기tok/s (RTX 4070)tok/s (M3 MacBook)용도
Qwen2.5-0.5B-Instruct0.5B350MB (Q4)8545분류, 추출
Qwen2.5-1.5B-Instruct1.5B900MB (Q4)4222짧은 텍스트 생성
SmolLM2-1.7B-Instruct1.7B1.0GB (Q4)3820범용 채팅
Phi-3.5-mini-instruct3.8B2.1GB (Q4)189추론 작업
Gemma-2-2B-Instruct2.0B1.2GB (Q4)2814지시 수행
Llama-3.2-1B-Instruct1.2B750MB (Q4)5228빠른 범용

경험칙: 인터랙티브 브라우저 UI에선 초당 20토큰 이상이 필요해요. 이러면 일반 하드웨어에서 2B 파라미터 이하 모델로 제한돼요. 3B+ 모델도 돌아가지만 실시간 채팅에선 답답하게 느껴져요.

양자화: 크기와 속도의 트레이드오프

양자화는 모델 정밀도를 32비트 부동소수점에서 더 작은 표현으로 줄이는 거예요:

fp32 (32비트) → fp16 (16비트) → int8 (8비트) → int4 (4비트)
   풀 사이즈  →     절반    →    1/4     →    1/8
   최고 품질  →              →            → 가장 빠르고 작음

품질 영향 (Qwen2.5-1.5B MMLU 벤치마크):

정밀도모델 크기MMLU 점수토큰/초메모리 사용
fp163.0 GB61.8123.4 GB
int81.5 GB61.2281.8 GB
int4 (Q4)900 MB59.1421.2 GB
int4 (Q4_K_M)950 MB60.3401.3 GB

Q4_K_M 혼합 양자화가 최적이에요 — 어텐션 레이어는 높은 정밀도를 유지하면서 피드포워드 레이어를 공격적으로 양자화해서, 1/3 크기에 97% 품질을 보존해요.

진행률 추적이 있는 모델 로딩

유저가 다운로드 진행 상황을 볼 수 있어야 해요:

import { AutoTokenizer, AutoModelForCausalLM, TextStreamer } from '@huggingface/transformers'; interface LoadingProgress { status: 'downloading' | 'loading' | 'ready'; file?: string; progress?: number; loaded?: number; total?: number; } async function loadModel( modelId: string, onProgress: (progress: LoadingProgress) => void ): Promise<{ model: any; tokenizer: any }> { onProgress({ status: 'downloading' }); const tokenizer = await AutoTokenizer.from_pretrained(modelId, { progress_callback: (data: any) => { if (data.status === 'progress') { onProgress({ status: 'downloading', file: data.file, progress: data.progress, loaded: data.loaded, total: data.total, }); } }, }); const model = await AutoModelForCausalLM.from_pretrained(modelId, { device: 'webgpu', dtype: 'q4', progress_callback: (data: any) => { if (data.status === 'progress') { onProgress({ status: 'downloading', file: data.file, progress: data.progress, loaded: data.loaded, total: data.total, }); } }, }); onProgress({ status: 'ready' }); return { model, tokenizer }; }

Chrome 내장 AI API

Chrome 131+에서 실험적 내장 AI API가 도입됐어요. 브라우저 네이티브 API로 Gemini Nano(소형 온디바이스 모델)를 사용할 수 있어요. 모델 다운로드 없이, 라이브러리 없이. Chrome 자체에 모델이 탑재돼 있어요.

Prompt API

// 가용성 체크 const capabilities = await self.ai.languageModel.capabilities(); console.log(capabilities.available); // 'readily', 'after-download', 'no' if (capabilities.available !== 'no') { // 세션 생성 const session = await self.ai.languageModel.create({ systemPrompt: 'You are a helpful coding assistant. Be concise.', temperature: 0.7, topK: 40, }); // 심플 프롬프트 const result = await session.prompt('자바스크립트에서 클로저가 뭐야?'); console.log(result); // 스트리밍 const stream = session.promptStreaming('WebGPU를 간단히 설명해줘.'); for await (const chunk of stream) { process.stdout.write(chunk); } // 세션이 대화 맥락 유지 const followUp = await session.prompt('코드 예제도 보여줘.'); // 정리 session.destroy(); }

Summarization API

const summarizer = await self.ai.summarizer.create({ type: 'tl;dr', // 'tl;dr', 'key-points', 'teaser', 'headline' length: 'medium', // 'short', 'medium', 'long' format: 'plain-text', // 'plain-text', 'markdown' }); const summary = await summarizer.summarize(longArticleText); console.log(summary);

Translation API

const translator = await self.ai.translator.create({ sourceLanguage: 'en', targetLanguage: 'ko', }); const translated = await translator.translate('Hello, world!'); console.log(translated); // 안녕, 세계!

Chrome 내장 AI vs Transformers.js 비교

기준Chrome 내장 AITransformers.js
모델 다운로드없음 (Chrome에 포함)첫 로드 시 350MB-2GB
셋업 복잡도3줄 코드npm install + 설정
모델 선택Gemini Nano만1,200개 이상 모델
브라우저 지원Chrome만모든 WebGPU 브라우저
품질 (GPT-4 대비)~60%모델별 상이 (50-75%)
작업 유연성텍스트, 이미지, 오디오 (멀티모달)텍스트, 비전, 오디오, 임베딩
파인튜닝불가능커스텀 ONNX 모델 가능
오프라인✅ Chrome 설치 후✅ 모델 캐시 후

추천: 빠른 프로토타입이나 Chrome 전용 기능에는 내장 AI를, 크로스 브라우저 지원이나 특정 모델, 텍스트 외 모달리티가 필요하면 Transformers.js를 쓰세요.

프로덕션 패턴

패턴 1: Web Worker 격리

메인 스레드에서 절대 추론을 돌리면 안 돼요. GPU 컴퓨트가 이벤트 루프를 막아서 UI가 얼어요.

// ai-worker.ts — Web Worker에서 실행 import { pipeline } from '@huggingface/transformers'; let generator: any = null; self.onmessage = async (e: MessageEvent) => { const { type, payload } = e.data; switch (type) { case 'LOAD': { self.postMessage({ type: 'STATUS', status: 'loading' }); generator = await pipeline('text-generation', payload.model, { device: 'webgpu', dtype: 'q4', progress_callback: (progress: any) => { self.postMessage({ type: 'PROGRESS', progress }); }, }); self.postMessage({ type: 'STATUS', status: 'ready' }); break; } case 'GENERATE': { if (!generator) { self.postMessage({ type: 'ERROR', error: '모델이 로드되지 않았습니다' }); return; } const result = await generator(payload.prompt, { max_new_tokens: payload.maxTokens ?? 256, temperature: payload.temperature ?? 0.7, do_sample: true, }); self.postMessage({ type: 'RESULT', text: result[0].generated_text, }); break; } } }; // main.ts — 앱에서 사용 class BrowserAI { private worker: Worker; private pending = new Map<string, (value: any) => void>(); constructor() { this.worker = new Worker( new URL('./ai-worker.ts', import.meta.url), { type: 'module' } ); this.worker.onmessage = (e) => { // 응답 처리 }; } async load(model: string): Promise<void> { this.worker.postMessage({ type: 'LOAD', payload: { model } }); // 'ready' 상태 대기... } async generate(prompt: string, options = {}): Promise<string> { this.worker.postMessage({ type: 'GENERATE', payload: { prompt, ...options }, }); // 결과 대기... return ''; } }

패턴 2: 스트리밍 토큰 생성

채팅 UI에서는 토큰이 생성되는 대로 스트리밍하세요:

import { AutoTokenizer, AutoModelForCausalLM, TextStreamer } from '@huggingface/transformers'; async function* streamGenerate( model: any, tokenizer: any, prompt: string, maxTokens: number = 256, ): AsyncGenerator<string> { const inputs = tokenizer(prompt, { return_tensors: 'pt' }); // 토큰을 yield하는 커스텀 스트리머 const tokens: string[] = []; let resolveNext: ((value: string) => void) | null = null; const streamer = new TextStreamer(tokenizer, { skip_prompt: true, callback_function: (text: string) => { if (resolveNext) { resolveNext(text); resolveNext = null; } else { tokens.push(text); } }, }); // 생성 시작 (백그라운드 실행) const generatePromise = model.generate({ ...inputs, max_new_tokens: maxTokens, temperature: 0.7, do_sample: true, streamer, }); // 토큰이 도착하는 대로 yield while (true) { if (tokens.length > 0) { yield tokens.shift()!; } else { const token = await new Promise<string>((resolve) => { resolveNext = resolve; }); yield token; } } await generatePromise; } // React 컴포넌트에서 사용 function ChatMessage({ prompt }: { prompt: string }) { const [text, setText] = useState(''); useEffect(() => { (async () => { for await (const token of streamGenerate(model, tokenizer, prompt)) { setText(prev => prev + token); } })(); }, [prompt]); return <p>{text}</p>; }

패턴 3: 서버 폴백을 곁들인 그레이스풀 디그레이데이션

모든 유저가 WebGPU를 지원하는 건 아니에요. 폴백 체인을 만드세요:

type AIBackend = 'webgpu' | 'wasm' | 'server'; async function detectBestBackend(): Promise<AIBackend> { // 1. WebGPU 시도 if (navigator.gpu) { const adapter = await navigator.gpu.requestAdapter(); if (adapter) { const device = await adapter.requestDevice(); if (device.limits.maxBufferSize >= 256 * 1024 * 1024) { return 'webgpu'; } } } // 2. WASM 폴백 (CPU 전용, 느리지만 범용) if (typeof WebAssembly !== 'undefined') { return 'wasm'; } // 3. 최후 수단: 서버 사이드 return 'server'; } async function createAIClient(): Promise<AIClient> { const backend = await detectBestBackend(); switch (backend) { case 'webgpu': return new BrowserAIClient({ device: 'webgpu', model: 'onnx-community/Qwen2.5-0.5B-Instruct' }); case 'wasm': return new BrowserAIClient({ device: 'wasm', model: 'onnx-community/Qwen2.5-0.5B-Instruct', // WASM은 5-10배 느리지만 어디서든 동작 }); case 'server': return new ServerAIClient({ endpoint: '/api/ai/generate' }); } }

패턴 4: 스마트 모델 캐싱

모델은 커요. 재다운로드를 피하려면 제대로 캐싱하세요:

class ModelCache { private cacheName = 'ai-models-v1'; async getCacheInfo(): Promise<{ models: string[]; totalSize: number; }> { const cache = await caches.open(this.cacheName); const keys = await cache.keys(); let totalSize = 0; const models: string[] = []; for (const request of keys) { const response = await cache.match(request); if (response) { const blob = await response.blob(); totalSize += blob.size; models.push(new URL(request.url).pathname); } } return { models, totalSize }; } async clearOldModels(maxCacheSizeMB: number = 2048): Promise<void> { const { totalSize } = await this.getCacheInfo(); if (totalSize > maxCacheSizeMB * 1024 * 1024) { await caches.delete(this.cacheName); console.log(`모델 캐시 정리 (기존 ${(totalSize / 1024 / 1024).toFixed(0)}MB)`); } } async isModelCached(modelId: string): Promise<boolean> { const cache = await caches.open(this.cacheName); const keys = await cache.keys(); return keys.some(k => k.url.includes(modelId)); } } // 캐시 상태에 따른 UI 표시 async function initAI() { const cache = new ModelCache(); const isCached = await cache.isModelCached('Qwen2.5-0.5B-Instruct'); if (isCached) { // 즉시 로드 — 이미 다운로드된 모델 showStatus('캐시에서 AI 모델 로딩 중...'); // 다운로드 30-60초 vs 캐시 2-5초 } else { // 첫 다운로드 필요 showStatus('AI 모델 다운로드 중 (350MB)...'); showProgressBar(); } }

오늘 바로 되는 실전 유스케이스

브라우저에서 모든 AI 유스케이스가 되는 건 아니에요. 지금 잘 되는 것들을 정리했어요:

1. 스마트 자동완성

// 빠르고 로컬인 텍스트 입력 자동완성 const completer = await pipeline('text-generation', 'onnx-community/Qwen2.5-0.5B-Instruct', { device: 'webgpu', dtype: 'q4' } ); async function autocomplete(partial: string): Promise<string[]> { const prompt = `이 문장을 자연스럽게 완성해줘: "${partial}"`; const results = await completer(prompt, { max_new_tokens: 30, num_return_sequences: 3, temperature: 0.8, do_sample: true, }); return results.map((r: any) => r.generated_text.replace(prompt, '').trim() ); }

2. 클라이언트 사이드 텍스트 분류

// 스팸 감지, 감성 분석, 콘텐츠 모더레이션 — API 콜 없이 const classifier = await pipeline('zero-shot-classification', 'Xenova/mobilebert-uncased-mnli', { device: 'webgpu' } ); async function classifyContent(text: string): Promise<{ label: string; score: number; }> { const result = await classifier(text, [ 'spam', 'legitimate', 'positive', 'negative', 'neutral', 'question', 'statement', ]); return { label: result.labels[0], score: result.scores[0], }; }

3. 로컬 임베딩 검색

// 완전 클라이언트 사이드 임베딩 — 로컬 검색에 제격 const embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', { device: 'webgpu' } ); async function embed(text: string): Promise<number[]> { const result = await embedder(text, { pooling: 'mean', normalize: true, }); return Array.from(result.data); } // API 콜 없는 로컬 검색 인덱스 async function localSearch( query: string, documents: string[] ): Promise<{ doc: string; score: number }[]> { const queryEmbedding = await embed(query); const docEmbeddings = await Promise.all(documents.map(embed)); return docEmbeddings .map((docEmb, i) => ({ doc: documents[i], score: cosineSimilarity(queryEmbedding, docEmb), })) .sort((a, b) => b.score - a.score); }

4. 실시간 번역

// API 콜 없는 번역 — 채팅 앱에 딱 const translator = await pipeline('translation', 'Xenova/nllb-200-distilled-600M', { device: 'webgpu', dtype: 'q4' } ); async function translate( text: string, from: string, to: string ): Promise<string> { const result = await translator(text, { src_lang: from, tgt_lang: to, max_length: 512, }); return result[0].translation_text; }

성능 최적화

워밍업 추론

모델 로드 후 첫 추론은 항상 가장 느려요 (WebGPU 파이프라인 컴파일 때문). 워밍업을 돌리세요:

async function warmUp(model: any, tokenizer: any): Promise<void> { const dummyInput = tokenizer('warmup', { return_tensors: 'pt' }); await model.generate({ ...dummyInput, max_new_tokens: 1, }); // 이후 실제 추론이 2-3배 빨라짐 }

KV 캐시 관리

멀티턴 대화에서 이전 토큰의 재계산을 피하려면 KV 캐시를 관리하세요:

interface ConversationState { pastKeyValues: any; tokenCount: number; } async function continueConversation( model: any, tokenizer: any, newMessage: string, state: ConversationState | null, ): Promise<{ response: string; newState: ConversationState }> { const inputs = tokenizer(newMessage, { return_tensors: 'pt' }); const generation = await model.generate({ ...inputs, max_new_tokens: 256, past_key_values: state?.pastKeyValues ?? null, // 이전 턴의 캐시된 연산 재사용 }); return { response: tokenizer.decode(generation[0], { skip_special_tokens: true }), newState: { pastKeyValues: generation.past_key_values, tokenCount: (state?.tokenCount ?? 0) + inputs.input_ids.length, }, }; }

메모리 압력 모니터링

브라우저는 메모리를 너무 많이 쓰는 탭을 강제 종료해요. 모니터링하고 대응하세요:

function monitorMemory(thresholdMB: number = 1500): void { if ('memory' in performance) { const memInfo = (performance as any).memory; const usedMB = memInfo.usedJSHeapSize / 1024 / 1024; const limitMB = memInfo.jsHeapSizeLimit / 1024 / 1024; console.log(`메모리: ${usedMB.toFixed(0)}MB / ${limitMB.toFixed(0)}MB`); if (usedMB > thresholdMB) { console.warn('메모리 사용량 높음 — 모델 언로드 고려'); } } } setInterval(() => monitorMemory(), 10000);

흔한 함정

함정 1: 메인 스레드 블로킹

가장 흔한 실수예요. WebGPU를 쓰더라도 모델 로딩과 토크나이제이션은 CPU에서 일어나고, 몇 초간 UI를 얼릴 수 있어요.

// ❌ 비추: 메인 스레드에서 로딩 const model = await pipeline('text-generation', 'model-id'); // 다운로드 + 초기화 동안 UI 멈춤 // ✅ 추천: Web Worker + 로딩 UI const worker = new Worker(new URL('./ai-worker.ts', import.meta.url)); worker.postMessage({ type: 'LOAD', model: 'model-id' }); // 워커가 초기화하는 동안 로딩 스피너 표시

함정 2: 모델 워밍업 무시

첫 추론은 WebGPU 파이프라인 컴파일 때문에 항상 2-5배 느려요. 유저는 여러분 앱 탓을 해요.

// ❌ 비추: 첫 유저 프롬프트에 느린 응답 // 유저가 입력하고 3초 대기 → 나쁜 UX // ✅ 추천: 로드 직후 워밍업 await loadModel(); await warmUp(model, tokenizer); // GPU 파이프라인 사전 컴파일 // 첫 유저 프롬프트도 일관되게 빠름

함정 3: 미지원 브라우저 폴백 없음

웹 유저의 ~15%가 아직 WebGPU를 지원하지 않아요 (구형 브라우저, iOS Safari, 일부 모바일).

// ❌ 비추: WebGPU 가용 가정 const model = await pipeline('text-generation', 'model', { device: 'webgpu' }); // 미지원 브라우저에서 크래시 // ✅ 추천: 점진적 향상 const backend = await detectBestBackend(); if (backend === 'server') { showMessage('현재 브라우저에서는 서버 AI를 사용합니다. Chrome으로 업그레이드하면 더 빠르고 프라이빗한 AI를 경험할 수 있어요.'); }

함정 4: 페이지 로드 시 모델 다운로드

유저가 요청하지 않은 900MB 다운로드는 적대적 UX예요.

// ❌ 비추: 페이지 로드 시 자동 다운로드 window.onload = () => loadModel('900MB-model'); // 유저 대역폭 파괴, 모바일 데이터 요금 폭탄 // ✅ 추천: 유저 액션에 의한 온디맨드 로드 document.getElementById('ai-btn')!.onclick = async () => { showConfirmation('AI 모델을 다운로드할까요? (900MB) 이후 방문에서는 캐시됩니다.'); // 유저가 확인한 후에만 다운로드 };

의사결정 프레임워크

브라우저에서 AI가 필요한가?
    ↓
고빈도·저복잡도 작업인가?
    ↓ Yes                    ↓ No
    ↓                        → 서버 API 사용
프라이버시가 중요한가?
    ↓ Yes              ↓ No
    ↓                   → 서버 API 고려
    ↓                     (더 간단하고 능력도 높음)
    ↓
500MB-2GB 첫 로드 다운로드를 감당할 수 있는가?
    ↓ Yes              ↓ No
    ↓                   → Chrome 내장 AI
    ↓                     (다운로드 없음, Chrome 전용)
    ↓
Transformers.js + WebGPU 사용
    ↓
인터랙티브 속도를 위해 2B 파라미터 이하 모델
    ↓
Web Worker + 서버 폴백으로 배포

결론

2026년 브라우저 AI는 진짜이고, 실용적이고, 프로덕션 준비가 됐어요 — 단, 조건부로요. 서버 사이드 AI를 대체하는 게 아니라, 특정 유스케이스에서 빛나는 보완 레이어예요.

최적 포지션: 고빈도, 프라이버시 민감, 레이턴시 크리티컬 작업에서 API 콜 비용이 안 맞을 때. 자동완성, 분류, 로컬 검색, 콘텐츠 모더레이션, 실시간 번역 — 이런 것들은 WebGPU 위의 2B 이하 모델로 아름답게 돌아가요.

할 일 정리:

  1. "브라우저에 AI 넣자"가 아니라 구체적 유스케이스부터. 로컬 추론이 진짜 문제를 해결하는 기능 하나를 고르세요 (비용, 프라이버시, 레이턴시).

  2. 첫 모델은 Qwen2.5-0.5B이나 Llama-3.2-1B으로. 둘 다 빠르고, 대부분의 작업에 충분하고, Q4 양자화로 브라우저 메모리에 편하게 들어가요.

  3. 반드시 Web Worker를 쓰세요. 예외 없어요. 메인 스레드 추론은 뻑뻑한 UI로 직행이에요.

  4. 폴백 체인을 만드세요. WebGPU → WASM → 서버. 유저 브라우저가 WebGPU를 지원한다고 절대 가정하지 마세요.

  5. 모델을 허락 없이 다운로드하지 마세요. 크기를 보여주고 명시적 동의를 받는 건 기본적인 UX 예절이에요.

"데이터센터가 필요한 AI"와 "브라우저 탭에서 돌아가는 AI" 사이의 간극이 빠르게 좁아지고 있어요. 모델은 더 작고 똑똑해지고 있고, 런타임(WebGPU)은 더 빨라지고 있고, 도구(Transformers.js)는 더 매끄러워지고 있어요. 맞는 유스케이스에서 클라이언트 사이드 AI는 미래가 아니에요 — 이미 최선의 선택이에요.

AIWebGPULLMTransformers.jsONNXChromebrowseredge-AIprivacyTypeScript

관련 도구 둘러보기

Pockit의 무료 개발자 도구를 사용해 보세요