반응형
API 키 없고, 서버 없고, 토큰 비용 없습니다. Llama·Gemma·Phi가 사용자 브라우저 GPU에서 직접 돌아갑니다. 프롬프트가 외부로 나가지 않습니다. 2026년 기준 브라우저가 AI 추론 런타임이 됐습니다.
[핵심 요약]
→ WebLLM: MLC AI(Carnegie Mellon·SJTU·NVIDIA)가 만든 오픈소스 브라우저 LLM 라이브러리
→ 동작 원리: WebGPU로 GPU 직접 접근 → 네이티브 수준 추론 속도
→ OpenAI API 호환: chat.completions.create() 그대로 사용
→ 지원 모델: Llama 3.2, Phi-3.5, Gemma 2, Mistral, Qwen 등
→ 브라우저 지원: Chrome·Edge·Firefox·Safari 기본 활성화 (2025년 말부터)
→ 대안: Transformers.js (Hugging Face), Chrome Built-in AI (Gemini Nano 내장)
→ 한계: 첫 로드 500MB~2GB 다운로드, 모바일 미지원, WebGPU 필요
→ 최적 케이스: 프라이버시 필수 앱, 오프라인 PWA, Chrome 익스텐션
왜 지금 브라우저에서 LLM이 가능한가
2023~2024 (불가능):
→ WebGL — 그래픽 전용, 행렬 연산 불가
→ 모델 크기 수십GB → 브라우저 메모리 초과
→ 브라우저 GPU 접근 API 없음
2026 (가능):
→ WebGPU — GPGPU 연산 지원, CUDA·Metal 대신 단일 API
→ 양자화(Quantization) — 7B 모델을 2~4GB로 압축
→ 스트리밍 가중치 로딩 — 전체 다운 전 추론 시작
→ Chrome·Edge·Firefox·Safari 모두 WebGPU 기본 활성화
→ 소비자 GPU 성능 향상 — M1/M2/M3, RTX 30/40 시리즈
[3가지 브라우저 LLM 선택지]
WebLLM:
→ 최고 성능 (WebGPU 풀 활용)
→ 모델 선택 자유 (Llama, Phi, Gemma 등)
→ OpenAI API 호환
→ 500MB~2GB 첫 다운로드
Transformers.js (Hugging Face):
→ WebGPU + WebAssembly 둘 다 지원
→ Hugging Face 모델 직접 사용
→ 임베딩, 분류, 번역 등 다양한 태스크
→ 더 넓은 브라우저 호환성
Chrome Built-in AI (Gemini Nano):
→ 다운로드 없음 (Chrome에 내장)
→ Chrome 전용
→ 모델 선택 불가 (Gemini Nano 고정)
→ 가장 빠른 시작, 가장 제한적
실전 1 — WebLLM 기본 세팅
# npm 프로젝트
npm install @mlc-ai/web-llm
# CDN (빠른 프로토타이핑)
# <script type="module"> 안에서 import
// 3줄로 시작하는 WebLLM
import { CreateMLCEngine } from "@mlc-ai/web-llm";
// 모델 로드 (첫 실행 시 다운로드, 이후 IndexedDB 캐시)
const engine = await CreateMLCEngine("Llama-3.2-1B-Instruct-q4f16_1-MLC");
// OpenAI API와 완전 동일한 인터페이스
const reply = await engine.chat.completions.create({
messages: [
{ role: "system", content: "한국어로 답변해줘." },
{ role: "user", content: "WebLLM이 뭐야?" }
],
});
console.log(reply.choices[0].message.content);
// 진행률 표시 + 스트리밍 응답
import { CreateMLCEngine } from "@mlc-ai/web-llm";
async function loadModel() {
const engine = await CreateMLCEngine(
"Llama-3.2-3B-Instruct-q4f16_1-MLC",
{
// 다운로드 진행률 콜백
initProgressCallback: (progress) => {
const pct = Math.round(progress.progress * 100);
document.getElementById("progress").textContent =
`모델 로딩 중... ${pct}% (${progress.text})`;
}
}
);
return engine;
}
async function streamChat(engine, userMessage) {
const chunks = await engine.chat.completions.create({
messages: [{ role: "user", content: userMessage }],
stream: true, // 스트리밍 활성화
});
let fullResponse = "";
for await (const chunk of chunks) {
const delta = chunk.choices[0]?.delta?.content ?? "";
fullResponse += delta;
// 실시간 UI 업데이트
document.getElementById("output").textContent = fullResponse;
}
return fullResponse;
}
[모델 선택 가이드 — 2026년 기준]
빠름 + 가벼움 (저사양 GPU):
→ Llama-3.2-1B-Instruct-q4f16_1-MLC (~700MB)
→ Phi-3.5-mini-instruct-q4f16_1-MLC (~2.2GB)
→ 추론 속도: 15~30 tok/s (M2 MacBook 기준)
균형 (일반 GPU):
→ Llama-3.2-3B-Instruct-q4f16_1-MLC (~1.8GB)
→ Gemma-2-2b-it-q4f16_1-MLC (~1.4GB)
→ 추론 속도: 10~20 tok/s
고성능 (RTX 3080+ / M3 Pro+):
→ Llama-3.1-8B-Instruct-q4f32_1-MLC (~4.5GB)
→ Mistral-7B-Instruct-v0.3-q4f16_1-MLC (~4.1GB)
→ 추론 속도: 5~15 tok/s
실전 2 — Web Worker로 UI 블로킹 방지
LLM 추론은 무겁습니다. 메인 스레드에서 돌리면 UI가 얼어붙습니다. Web Worker로 분리합니다.
// worker.js — 별도 워커 파일
import { CreateMLCEngine } from "@mlc-ai/web-llm";
let engine = null;
self.onmessage = async (event) => {
const { type, payload } = event.data;
if (type === "LOAD_MODEL") {
engine = await CreateMLCEngine(payload.model, {
initProgressCallback: (progress) => {
// 메인 스레드로 진행률 전송
self.postMessage({
type: "PROGRESS",
payload: { pct: Math.round(progress.progress * 100) }
});
}
});
self.postMessage({ type: "MODEL_READY" });
}
if (type === "CHAT") {
const chunks = await engine.chat.completions.create({
messages: payload.messages,
stream: true,
});
for await (const chunk of chunks) {
const delta = chunk.choices[0]?.delta?.content ?? "";
if (delta) {
self.postMessage({ type: "CHUNK", payload: { delta } });
}
}
self.postMessage({ type: "DONE" });
}
};
// main.js — 메인 스레드
const worker = new Worker(new URL("./worker.js", import.meta.url), {
type: "module"
});
// 워커 메시지 처리
worker.onmessage = (event) => {
const { type, payload } = event.data;
switch (type) {
case "PROGRESS":
updateProgressBar(payload.pct);
break;
case "MODEL_READY":
enableChatUI();
break;
case "CHUNK":
appendToOutput(payload.delta); // 실시간 스트리밍
break;
case "DONE":
finalizeChatMessage();
break;
}
};
// 모델 로드
worker.postMessage({
type: "LOAD_MODEL",
model: "Llama-3.2-3B-Instruct-q4f16_1-MLC"
});
// 채팅 전송
function sendMessage(userInput) {
worker.postMessage({
type: "CHAT",
payload: {
messages: [
{ role: "system", content: "친절하게 답변해줘." },
{ role: "user", content: userInput }
]
}
});
}
실전 3 — React 앱에 통합
// useWebLLM.ts — 커스텀 훅
import { useState, useEffect, useRef } from "react";
import { CreateMLCEngine, MLCEngine } from "@mlc-ai/web-llm";
interface Message {
role: "user" | "assistant" | "system";
content: string;
}
export function useWebLLM(modelId: string) {
const [engine, setEngine] = useState<MLCEngine | null>(null);
const [loading, setLoading] = useState(false);
const [progress, setProgress] = useState(0);
const [streaming, setStreaming] = useState(false);
useEffect(() => {
setLoading(true);
CreateMLCEngine(modelId, {
initProgressCallback: (p) => setProgress(Math.round(p.progress * 100))
}).then((eng) => {
setEngine(eng);
setLoading(false);
});
}, [modelId]);
const chat = async (
messages: Message[],
onChunk: (delta: string) => void
) => {
if (!engine) return;
setStreaming(true);
const chunks = await engine.chat.completions.create({
messages,
stream: true,
});
for await (const chunk of chunks) {
const delta = chunk.choices[0]?.delta?.content ?? "";
if (delta) onChunk(delta);
}
setStreaming(false);
};
return { engine, loading, progress, streaming, chat };
}
// ChatApp.tsx
export function ChatApp() {
const { loading, progress, streaming, chat } = useWebLLM(
"Phi-3.5-mini-instruct-q4f16_1-MLC"
);
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [response, setResponse] = useState("");
const handleSend = async () => {
const userMsg: Message = { role: "user", content: input };
const history = [...messages, userMsg];
setMessages(history);
setInput("");
setResponse("");
await chat(history, (delta) => {
setResponse(prev => prev + delta);
});
setMessages(prev => [
...prev,
{ role: "assistant", content: response }
]);
};
if (loading) return <div>모델 로딩 중... {progress}%</div>;
return (
<div>
<div className="messages">
{messages.map((m, i) => (
<div key={i} className={m.role}>{m.content}</div>
))}
{streaming && <div className="assistant">{response}</div>}
</div>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={handleSend} disabled={streaming}>전송</button>
</div>
);
}
실전 4 — Chrome 익스텐션 AI
WebLLM의 강력한 사용 케이스입니다. 백그라운드 서비스 워커에서 LLM을 돌리고 모든 탭이 공유합니다.
// background.js (Service Worker)
import { CreateMLCEngine } from "@mlc-ai/web-llm";
let engine = null;
// 익스텐션 설치 시 모델 로드
chrome.runtime.onInstalled.addListener(async () => {
engine = await CreateMLCEngine("Phi-3.5-mini-instruct-q4f16_1-MLC");
console.log("WebLLM 준비 완료");
});
// content script / popup에서 메시지 수신
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === "SUMMARIZE") {
(async () => {
const reply = await engine.chat.completions.create({
messages: [
{
role: "user",
content: `다음 텍스트를 3줄로 요약해줘:\n\n${request.text}`
}
],
});
sendResponse({ summary: reply.choices[0].message.content });
})();
return true; // 비동기 응답
}
});
// content.js — 웹페이지에서 선택한 텍스트 요약
document.addEventListener("mouseup", async () => {
const selected = window.getSelection()?.toString().trim();
if (!selected || selected.length < 100) return;
const response = await chrome.runtime.sendMessage({
type: "SUMMARIZE",
text: selected
});
// 선택 영역 옆에 요약 툴팁 표시
showTooltip(response.summary);
});
[Chrome 익스텐션 WebLLM의 장점]
→ 사용자 텍스트 서버 전송 없음 — 완전 프라이버시
→ 모델 한 번 로드 → 모든 탭 재사용
→ 오프라인 동작
→ API 비용 0원
→ 사용 케이스: 페이지 요약, 문법 교정, 번역, 코드 설명
WebGPU 미지원 폴백 — Transformers.js
WebGPU 없는 환경 (구형 브라우저, 일부 모바일)을 위한 폴백입니다.
// webgpu-check.js — 환경 감지 + 자동 폴백
async function createAIEngine() {
const hasWebGPU = "gpu" in navigator;
if (hasWebGPU) {
// WebGPU 지원 → WebLLM 사용
const { CreateMLCEngine } = await import("@mlc-ai/web-llm");
return await CreateMLCEngine("Llama-3.2-1B-Instruct-q4f16_1-MLC");
} else {
// WebGPU 미지원 → Transformers.js (WebAssembly 폴백)
const { pipeline } = await import("@xenova/transformers");
return await pipeline("text-generation", "Xenova/Phi-3-mini-4k-instruct");
}
}
// Chrome Built-in AI 감지
async function checkChromeBuiltinAI() {
if ("ai" in window && "languageModel" in window.ai) {
const status = await window.ai.languageModel.availability();
if (status === "available") {
// Gemini Nano 내장 — 다운로드 없음
return await window.ai.languageModel.create();
}
}
return null;
}
마무리
✅ WebLLM 써야 하는 경우
→ 사용자 데이터가 서버로 나가면 안 되는 앱 (의료, 법률, 금융)
→ API 비용 없애고 싶은 고트래픽 서비스
→ 오프라인 동작 필수 PWA
→ Chrome 익스텐션에 AI 추가
→ 토큰 비용 없이 무제한 추론 가능한 프로토타입
❌ WebLLM이 안 맞는 경우
→ 모바일 사용자가 많은 서비스 (WebGPU 모바일 미지원)
→ 최신 GPT/Claude 수준 품질 필요 (소형 모델 한계)
→ 첫 로드 2GB 다운로드가 UX에 치명적인 경우
→ 서버사이드 RAG, 외부 도구 연결 필요한 에이전트
→ 실시간 최신 정보 필요한 서비스
관련 글
반응형
'LLM' 카테고리의 다른 글
| 지금 쓰는 모델이 6개월 후엔 레거시다 — H2 2026 모델 로드맵 완전 정리 (0) | 2026.05.26 |
|---|---|
| Opus 4.7의 1/10 비용으로 동급 성능이 가능한가 — Cursor Composer 2.5 실전 분석 (0) | 2026.05.26 |
| Reasoning 모델이 기본이 된 2026 — 프롬프트 전략을 바꿔야 하는 이유 (0) | 2026.05.18 |
| IBM Granite 4.1 완전 분석 — 8B가 32B MoE를 이긴 이유, 파라미터보다 훈련이 중요하다 (0) | 2026.05.06 |
| 프롬프트 버전 관리 완전 가이드 — Git처럼 프롬프트를 관리하는 법 (0) | 2026.04.30 |