Workers AI로 엣지에서 AI 모델 돌리기: 요약 API, Vectorize RAG, D1 연동과 비용
이 글의 핵심
별도 GPU 서버 없이 Worker 안에서 AI 모델을 부를 수 있다는 점이 Workers AI의 장점이지만, 요청마다 과금되는 구조라 캐싱과 모델 선택에 따라 비용이 크게 달라집니다. 문서 임베딩과 검색 흐름, 응답 지연을 줄이는 스트리밍, 프로덕션 전에 확인할 체크리스트를 함께 정리했습니다.
GPU 서버 대신 사용량 과금을 택한다는 것
Cloudflare Workers AI로 Edge에서 AI 모델을 실행하는 글입니다. Workers AI, Vectorize, D1, R2를 활용한 실전 예제와 프로덕션 배포까지 다룹니다.
직접 모델을 서빙할 때의 부담
GPU 서버를 상시 운영하는 부담
직접 모델을 서빙하려면 GPU 인스턴스를 띄워 두어야 하고, 요청이 없는 새벽에도 시간당 요금이 나갑니다. 트래픽이 적거나 들쭉날쭉한 서비스일수록 유휴 비용의 비중이 커집니다.
스케일링과 운영
트래픽이 몰리면 인스턴스를 늘리고 모델을 다시 로드해야 하며, 드라이버·CUDA 버전·모델 가중치 관리도 직접 해야 합니다. Workers AI는 이 부분을 Cloudflare가 맡고, 사용한 만큼만 과금합니다.
네트워크 거리
Worker 코드는 사용자와 가까운 데이터센터에서 실행되고, AI 추론은 GPU가 배치된 Cloudflare 데이터센터 중 가까운 곳으로 라우팅됩니다. 외부 API를 부를 때처럼 해외 리전까지 왕복하는 구간이 줄어드는 것이 장점입니다. 다만 LLM 응답 시간의 대부분은 네트워크가 아니라 토큰 생성 시간이므로, “엣지라서 LLM 응답이 수십 ms에 온다”는 기대는 맞지 않습니다.
flowchart TB
subgraph Traditional[직접 서빙]
A1[사용자] --> A2[API 서버]
A2 --> A3[상시 운영 GPU 서버]
A3 --> A2 --> A1
end
subgraph Edge[Cloudflare Workers AI]
B1[사용자] --> B2[가까운 Worker]
B2 --> B3[GPU가 있는 가까운 데이터센터에서 추론]
B3 --> B2 --> B1
end
이 글은 Workers AI를 “공짜로 빠른 GPU”가 아니라 운영 부담을 사용량 과금으로 바꾸는 선택지로 보고, 그 대가(모델 선택의 제약, 요청당 과금, 한도)를 함께 살펴봅니다.
env.AI 바인딩과 뉴런 단위 과금
Cloudflare Workers AI는 Cloudflare 네트워크의 GPU에서 오픈 모델을 실행하고, Worker 코드에서 바인딩 하나(env.AI)로 호출할 수 있게 해 주는 서비스입니다. Worker 자체는 전 세계 거의 모든 Cloudflare 데이터센터에서 돌지만, GPU는 그중 일부 위치에 배치되어 있어 추론 요청은 가까운 GPU 위치로 보내집니다.
주요 기능:
- Workers AI: LLM, 임베딩, 이미지 생성, 음성 인식 등 여러 오픈 모델
- Vectorize: 벡터 데이터베이스 (RAG 구현)
- D1: SQLite 기반 Edge 데이터베이스
- R2: S3 호환 객체 스토리지
- KV: Key-Value 스토어 가격 구조 (정확한 단가는 공식 요금 페이지에서 확인):
- Workers AI: 뉴런(neuron) 단위 과금. 1,000 뉴런당 $0.011이며 하루 10,000 뉴런은 무료. 모델마다 입력·출력 토큰이 몇 뉴런으로 환산되는지가 다르게 정해져 있습니다.
- Vectorize: 저장된 벡터 차원 수와 쿼리한 벡터 차원 수 기준으로 과금
- D1: 읽은 행(rows read)과 쓴 행(rows written) 수, 저장 용량 기준으로 과금, 월 무료 할당량 있음
“뉴런”은 GPU 연산량을 추상화한 Cloudflare 고유의 과금 단위로, 모델 파라미터 수와 같은 개념이 아닙니다. 실제로는 모델별로 “입력 100만 토큰당 몇 뉴런, 출력 100만 토큰당 몇 뉴런”이 정해져 있으므로, 비용은 모델 크기와 토큰 수, 특히 출력 토큰 수에 따라 결정됩니다. 요금표는 자주 바뀌므로 설계 전에 대시보드의 모델별 가격을 확인하는 것이 안전합니다.
wrangler로 첫 AI Worker 만들기
설치
npm install -g wrangler
wrangler login
프로젝트 생성
npm create cloudflare@latest my-ai-app
cd my-ai-app
첫 번째 AI Worker
// src/index.ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{ role: 'user', content: '안녕하세요!' }
],
});
return Response.json(response);
},
};
# 로컬 실행
wrangler dev
# 배포
wrangler deploy
이 코드가 동작하려면 wrangler 설정 파일에 AI 바인딩이 있어야 합니다. npm create cloudflare에서 AI 템플릿을 고르면 자동으로 들어가지만, 기존 프로젝트에 추가한다면 직접 적어야 합니다. 빠뜨리면 env.AI가 undefined라 “Cannot read properties of undefined (reading ‘run’)” 에러가 납니다.
# wrangler.toml
[ai]
binding = "AI"
wrangler dev로 로컬 실행을 해도 AI 추론은 실제 Cloudflare GPU에서 실행되고 사용량이 계정에 과금됩니다. 로컬에는 모델이 없기 때문입니다. 개발 중 반복 호출로 무료 한도를 다 쓰는 일이 생각보다 흔하니, 테스트에서는 응답을 모킹하거나 작은 모델로 바꿔 두는 편이 좋습니다. 또 LLM 모델의 응답 형식은 { response: "..." }이지만 모델 종류(임베딩, 요약, 이미지)마다 입력·출력 스키마가 다르므로, 새 모델을 쓸 때는 문서의 스키마를 먼저 확인하세요. @cf/meta/llama-3-8b-instruct 같은 모델 ID는 새 버전이 나오면 사용 중단(deprecation)되기도 해서, 모델 ID를 설정 값으로 빼 두면 교체가 쉽습니다.
Llama로 텍스트 요약 API 만들기
// src/index.ts
interface Env {
AI: any;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// CORS
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
try {
const { text } = await request.json();
if (!text || text.length < 100) {
return Response.json(
{ error: '텍스트는 최소 100자 이상이어야 합니다' },
{ status: 400 }
);
}
// AI로 요약
const response = await env.AI.run('@cf/facebook/bart-large-cnn', {
input_text: text,
max_length: 150,
});
return Response.json({
summary: response.summary,
original_length: text.length,
summary_length: response.summary.length,
});
} catch (error) {
return Response.json(
{ error: 'Internal Server Error' },
{ status: 500 }
);
}
},
};
이 요약 API는 LLM이 아니라 요약 전용 모델(bart-large-cnn)을 씁니다. 범용 LLM에 “요약해 줘”라고 요청하는 것보다 출력이 짧고 예측 가능하며, 모델이 작아 뉴런 소비도 적은 편입니다. 대신 이 모델은 영어 뉴스 기사로 학습되어 한국어 텍스트는 제대로 요약하지 못합니다. 한국어 요약이 필요하다면 다국어를 지원하는 LLM에 요약 프롬프트를 주는 쪽이 현실적입니다. 모델을 고를 때 “이 모델이 어떤 언어와 도메인으로 학습됐는지”를 먼저 확인하는 것이 Workers AI에서 가장 자주 놓치는 부분입니다.
코드에서 챙겨야 할 점도 있습니다. 'Access-Control-Allow-Origin': '*'를 OPTIONS 응답에만 붙이고 실제 POST 응답에는 붙이지 않았기 때문에, 브라우저에서 호출하면 사전 요청은 통과해도 본 요청에서 CORS 에러가 납니다. 실제 응답에도 같은 헤더를 붙여야 합니다. 또 최소 길이만 검사하고 최대 길이는 검사하지 않는데, 모델마다 입력 토큰 한도가 있어 너무 긴 텍스트는 에러가 나거나 잘립니다. 공개 API라면 누구나 호출해 계정의 뉴런을 소모시킬 수 있으므로, 인증이나 Cloudflare의 Rate Limiting 규칙을 앞에 두어야 합니다. catch 블록이 원인을 버리는 것도 운영에서 불편하니, 최소한 console.error(error)로 wrangler tail에서 볼 수 있게 남기세요.
Vectorize 인덱스로 RAG 구현
Vectorize 생성
# 벡터 인덱스 생성
wrangler vectorize create my-vectors --dimensions=768 --metric=cosine
--dimensions=768은 임의의 숫자가 아니라 아래에서 쓰는 임베딩 모델 bge-base-en-v1.5의 출력 차원입니다. 인덱스의 차원은 생성 후 바꿀 수 없어서, 나중에 1024차원 모델로 바꾸려면 인덱스를 새로 만들고 모든 문서를 다시 임베딩해야 합니다. 차원이 맞지 않는 벡터를 넣으면 upsert가 차원 불일치 에러로 실패합니다. 한국어 문서를 다룬다면 이름대로 영어 전용인 bge-base-en-v1.5 대신 다국어 모델(@cf/baai/bge-m3 등, 1024차원)을 처음부터 고르는 편이 검색 품질이 훨씬 낫습니다. 인덱스를 Worker에서 쓰려면 wrangler 설정에 [[vectorize]] 바인딩(binding = "VECTORIZE", index_name = "my-vectors")도 추가해야 합니다.
RAG 구현
// src/rag.ts
interface Env {
AI: any;
VECTORIZE: VectorizeIndex;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { question } = await request.json();
// 1. 질문을 벡터로 변환
const embedding = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: question,
});
// 2. 유사한 문서 검색
const matches = await env.VECTORIZE.query(embedding.data[0], {
topK: 3,
returnMetadata: 'all', // 없으면 metadata가 응답에 포함되지 않음
});
// 3. 검색된 문서를 컨텍스트로 사용
const context = matches.matches
.map(m => m.metadata.text)
.join('\n\n');
// 4. LLM으로 답변 생성
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [
{
role: 'system',
content: `다음 문서를 참고하여 답변하세요:\n\n${context}`
},
{
role: 'user',
content: question
}
],
});
return Response.json({
answer: response.response,
sources: matches.matches.map(m => m.metadata),
});
},
};
RAG의 흐름은 네 단계입니다. 질문을 문서와 같은 임베딩 모델로 벡터로 바꾸고, 벡터 DB에서 가장 가까운 문서 조각을 찾고, 그 조각들을 프롬프트에 붙여 LLM이 “근거를 보고” 답하게 합니다. 임베딩 모델이 문서를 넣을 때와 질문할 때 달라지면 벡터 공간이 달라서 검색이 사실상 무작위가 됩니다. 에러는 나지 않고 엉뚱한 문서만 돌아오기 때문에 가장 찾기 어려운 버그입니다.
returnMetadata: 'all' 옵션은 제가 처음 이 코드를 돌렸을 때 빠져 있어 한참 헤맸던 부분입니다. Vectorize는 기본적으로 id와 점수만 돌려주므로, 이 옵션 없이 m.metadata.text에 접근하면 “Cannot read properties of undefined” 에러가 나거나 빈 컨텍스트로 LLM이 호출됩니다. 빈 컨텍스트여도 LLM은 그럴듯한 답을 만들어 내기 때문에, 검색이 실패한 것을 답변만 보고는 알아채기 어렵습니다. 개발 중에는 sources를 함께 반환해 실제로 어떤 문서가 검색됐는지 확인하는 습관이 중요합니다.
검색 품질을 좌우하는 것은 모델보다 청크 설계입니다. 문서 하나를 통째로 임베딩하면 여러 주제가 섞인 평균적인 벡터가 되어 검색이 잘 안 되고, 너무 잘게 자르면 문맥이 사라집니다. 보통 수백 토큰 단위로 자르고 앞뒤를 조금 겹치게 합니다. 또 유사도 점수(m.score)가 낮은 결과까지 무조건 컨텍스트에 넣으면 관련 없는 내용이 답변을 오염시키므로, 일정 점수 이하는 버리고 “관련 문서를 찾지 못했다”고 답하게 하는 편이 낫습니다. 더 깊은 설계는 RAG 가이드에서 다룹니다.
문서 임베딩 및 저장
// scripts/embed-docs.ts
const documents = [
{ id: '1', text: 'Cloudflare Workers는 Edge에서 실행됩니다.' },
{ id: '2', text: 'Workers AI는 80개 이상의 모델을 제공합니다.' },
{ id: '3', text: 'Vectorize는 벡터 데이터베이스입니다.' },
];
for (const doc of documents) {
// 임베딩 생성
const embedding = await env.AI.run('@cf/baai/bge-base-en-v1.5', {
text: doc.text,
});
// Vectorize에 저장
await env.VECTORIZE.upsert([
{
id: doc.id,
values: embedding.data[0],
metadata: { text: doc.text },
},
]);
}
이 스크립트는 흐름을 보여 주기 위한 것이라 그대로는 실행되지 않습니다. env.AI와 env.VECTORIZE는 Worker 런타임 안에서만 존재하므로, 로컬 Node 스크립트에서는 env가 정의되어 있지 않습니다. 실제로는 인증을 건 관리용 엔드포인트(예: POST /admin/ingest)를 Worker에 만들어 이 로직을 넣거나, Cloudflare REST API로 임베딩을 만들고 wrangler vectorize insert로 넣는 방식을 씁니다.
문서가 많다면 두 가지를 바꿔야 합니다. 임베딩 모델은 text에 배열을 받아 한 번의 호출로 여러 문서를 임베딩할 수 있고, upsert도 여러 벡터를 한 번에 받으므로 문서마다 두 번씩 호출하는 대신 수십 개씩 묶어 처리하는 편이 빠릅니다. 또 Vectorize의 삽입은 비동기로 반영되어, upsert 직후 바로 쿼리하면 새 문서가 검색되지 않을 수 있습니다. 적재 직후 테스트가 실패한다면 몇 초 뒤 다시 확인해 보세요. 메타데이터에 원문 전체를 넣는 방식은 간단하지만 메타데이터 크기 제한이 있으므로, 긴 원문은 D1이나 R2에 두고 메타데이터에는 id나 경로만 저장하는 구성이 일반적입니다.
D1에 AI 결과 저장하기
D1 생성
wrangler d1 create my-database
# wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-database-id"
스키마 생성
-- schema.sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE conversations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
message TEXT NOT NULL,
response TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
# 스키마 적용 (로컬 개발 DB)
wrangler d1 execute my-database --local --file=schema.sql
# 배포된 원격 DB에 적용
wrangler d1 execute my-database --remote --file=schema.sql
최근 wrangler는 d1 execute의 기본 대상이 로컬 개발용 DB입니다. --remote 없이 스키마를 적용하고 배포하면, 로컬에서는 잘 되던 Worker가 운영에서 “D1_ERROR: no such table: conversations”를 냅니다. 스키마가 자주 바뀐다면 wrangler d1 migrations create/apply로 마이그레이션 파일을 관리하는 편이 로컬과 원격의 상태를 맞추기 쉽습니다.
Worker에서 사용
// src/index.ts
interface Env {
AI: any;
DB: D1Database;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { userId, message } = await request.json();
// AI 응답 생성
const aiResponse = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [{ role: 'user', content: message }],
});
// 대화 저장
await env.DB.prepare(
'INSERT INTO conversations (user_id, message, response) VALUES (?, ?, ?)'
)
.bind(userId, message, aiResponse.response)
.run();
return Response.json({ response: aiResponse.response });
},
};
prepare().bind().run() 형태는 SQL과 값을 분리하는 파라미터 바인딩이라, message에 따옴표나 SQL 구문이 들어 있어도 SQL 인젝션이 일어나지 않습니다. 문자열을 이어 붙여 쿼리를 만드는 방식은 절대 쓰지 마세요. 이 예제는 userId를 요청 본문에서 그대로 받는데, 실제 서비스라면 인증 토큰에서 사용자를 확인해야 합니다. 그렇지 않으면 누구나 다른 사용자의 id로 대화를 기록할 수 있고, users에 없는 id를 넣으면 외래 키 제약 때문에 INSERT가 실패합니다(D1은 외래 키 검사가 기본으로 켜져 있습니다).
AI 호출 뒤 DB 저장을 await로 기다리는 만큼 응답이 늦어진다는 점도 개선할 수 있습니다. 저장 결과가 응답에 필요 없다면 ctx.waitUntil(env.DB.prepare(...).run())으로 응답을 먼저 보내고 저장은 백그라운드에서 끝내게 할 수 있습니다. 이 경우 fetch(request, env, ctx)처럼 세 번째 인자 ctx를 받아야 합니다.
스트리밍 응답과 Cache API로 체감 지연 줄이기
스트리밍 응답
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { messages } = await request.json();
const stream = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages,
stream: true,
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
},
});
},
};
LLM 응답은 수백 토큰을 생성하는 데 수 초가 걸리므로, 다 만들어질 때까지 기다렸다 한 번에 보내면 사용자는 빈 화면을 오래 봅니다. stream: true를 주면 env.AI.run이 JSON 대신 Server-Sent Events 형식의 ReadableStream을 반환하고, 이를 그대로 Response에 넘기면 토큰이 생성되는 대로 클라이언트에 전달됩니다. 전체 소요 시간은 같지만 첫 글자가 보이는 시간이 크게 줄어듭니다. 클라이언트는 EventSource(GET만 지원) 대신 보통 fetch의 response.body.getReader()로 읽으며, 각 줄은 data: {"response":"..."} 형태이고 마지막에 data: [DONE]이 옵니다.
스트리밍을 쓰면 위 D1 예제처럼 “완성된 응답을 DB에 저장”하는 것이 어려워집니다. 스트림을 tee()로 복제해 한쪽은 클라이언트로, 다른 쪽은 ctx.waitUntil 안에서 모아 저장하는 방식이 필요합니다.
캐싱
// KV로 응답 캐싱
interface Env {
AI: any;
CACHE: KVNamespace;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { prompt } = await request.json();
// 캐시 확인
const cached = await env.CACHE.get(prompt);
if (cached) {
return Response.json({ response: cached, cached: true });
}
// AI 실행
const response = await env.AI.run('@cf/meta/llama-3-8b-instruct', {
messages: [{ role: 'user', content: prompt }],
});
// 캐시 저장 (1시간)
await env.CACHE.put(prompt, response.response, {
expirationTtl: 3600,
});
return Response.json({ response: response.response, cached: false });
},
};
캐싱은 같은 질문이 반복되는 경우(FAQ 봇, 고정 문서 요약)에 비용을 가장 확실하게 줄이는 방법이지만, 이 코드를 그대로 쓰면 세 가지 문제를 만납니다. 첫째, KV의 키는 최대 512바이트라 긴 프롬프트를 그대로 키로 쓰면 put이 실패합니다. 한글은 한 글자가 UTF-8로 3바이트라 170자 정도만 넘어도 걸립니다. crypto.subtle.digest('SHA-256', ...)로 프롬프트를 해시해 키로 쓰는 것이 일반적입니다. 둘째, KV는 최종 일관성 저장소라 방금 쓴 값이 다른 지역에서 곧바로 읽히지 않을 수 있습니다. 캐시 용도로는 괜찮지만, 같은 질문이 동시에 여러 지역에서 들어오면 모두 캐시 미스로 모델을 호출합니다. 셋째, 프롬프트가 한 글자만 달라도 캐시가 빗나갑니다. 공백 정리나 소문자화 같은 정규화를 거쳐 키를 만들면 적중률이 올라갑니다.
LLM 응답을 캐싱한다는 것은 “같은 질문에는 항상 같은 답”을 준다는 뜻이기도 합니다. 사용자별 정보가 들어간 프롬프트를 캐싱하면 다른 사용자에게 남의 응답이 나가는 사고가 날 수 있으니, 개인화된 요청은 캐시 대상에서 빼거나 키에 사용자 범위를 포함해야 합니다. 여러 모델 공급자를 쓰거나 캐싱·로깅·재시도를 코드 없이 처리하고 싶다면 Cloudflare의 AI Gateway를 앞에 두는 방법도 있습니다.
뉴런 단가로 비용 추정하기
Workers AI 요금
// 비용 = 토큰 수 × 모델별 뉴런 환산율 × $0.011 / 1,000 뉴런
// - 뉴런은 모델 파라미터 수가 아니라 Cloudflare의 연산량 과금 단위
// - 모델마다 "입력 100만 토큰당 N 뉴런, 출력 100만 토큰당 M 뉴런"이 정해져 있음
// - 출력 토큰이 입력 토큰보다 비싸게 환산되는 경우가 대부분
// 예산 추정 순서
// 1) 요청당 평균 입력/출력 토큰 수 측정
// 2) 요금 페이지에서 사용할 모델의 토큰당 가격 확인
// 3) 일 요청 수를 곱하고, 하루 무료 10,000 뉴런을 뺀다
비용 추정에서 가장 흔한 실수는 출력 길이를 과소평가하는 것입니다. 대부분의 모델은 출력 토큰이 입력 토큰보다 비싸게 환산되고, 제한이 없으면 LLM은 필요 이상으로 길게 답합니다. max_tokens로 출력 길이를 제한하고 시스템 프롬프트에서 답변 길이를 지정하는 것만으로도 비용이 눈에 띄게 줄어듭니다. RAG에서는 반대로 입력이 커지는 쪽을 조심해야 합니다. 검색 결과 청크를 많이 붙일수록 매 요청의 입력 토큰이 늘어나므로, topK를 무작정 키우기보다 관련성 높은 몇 개만 넣는 것이 품질과 비용 모두에 유리합니다.
외부 상용 API와 비교할 때는 단가만 보지 말고 모델 품질을 함께 봐야 합니다. Workers AI의 오픈 모델은 요약·분류·임베딩·짧은 답변처럼 작은 모델로 충분한 작업에서 가성비가 좋고, 복잡한 추론이나 긴 한국어 글쓰기는 대형 상용 모델이 여전히 품질 면에서 앞서는 경우가 많습니다. 작업별로 모델을 나누는 하이브리드 구성이 흔한 이유입니다.
비용 최적화
// 1. 작은 모델 사용
const response = await env.AI.run('@cf/meta/llama-2-7b-chat-int8', {
// 양자화·소형 모델은 대체로 토큰당 뉴런 환산율이 낮음
});
// 2. 캐싱
await env.CACHE.put(key, value, { expirationTtl: 3600 });
// 3. 동시 처리 (비용은 같고, 전체 대기 시간만 줄어듦)
const responses = await Promise.all(
prompts.map(p => env.AI.run(model, { messages: [{ role: 'user', content: p }] }))
);
세 번째 항목은 “배치 처리”라고 불리곤 하지만 요청을 동시에 보낼 뿐이라 과금되는 뉴런은 따로 보낼 때와 같습니다. 이점은 전체 처리 시간이 줄어드는 것이고, 대신 계정의 분당 요청 한도(rate limit)에 걸리기 쉬워집니다. 한도를 넘으면 에러가 나므로, 수백 개를 한 번에 Promise.all로 보내기보다 몇 개씩 나눠 보내고 실패한 요청은 재시도하는 편이 안전합니다. 대량의 비실시간 작업이라면 Queues로 받아 순차 처리하는 구성이 더 적합합니다.
Workers AI 핵심 정리
- Cloudflare Workers AI: Cloudflare 네트워크의 GPU에서 오픈 모델 실행, Worker 바인딩으로 호출
- 지연: 네트워크 구간은 짧지만, LLM 응답 시간은 대부분 토큰 생성 시간이므로 스트리밍으로 체감 지연을 줄임
- 다양한 모델: LLM, 임베딩, 이미지 생성, 음성 인식 등 (모델별 언어·입력 한도 확인 필요)
- Vectorize: 벡터 DB로 RAG 구현
- D1: Edge 데이터베이스
- 비용 효율: 요청(뉴런) 단위 과금이라 트래픽이 적거나 들쭉날쭉한 서비스에서는 GPU 서버를 상시 띄우는 것보다 부담이 작을 수 있음 (절감 폭은 사용량에 따라 다름)
같이 보면 좋은 글
- WebAssembly AI | 브라우저에서 LLM 실행
- Cloudflare Workers 가이드
- RAG(검색 증강 생성) 가이드
- ChatGPT API 실전 가이드 | OpenAI API로 AI 애플리케이션 만들기
- Cloudflare Pages 배포
자주 묻는 질문 (FAQ)
Q. Cloudflare Workers AI 비용은 얼마인가요?
A. 1,000 뉴런당 $0.011이고 하루 10,000 뉴런까지 무료입니다. 뉴런은 모델 파라미터 수가 아니라 연산량 단위라서, 실제 비용은 사용하는 모델의 토큰당 환산율과 입력·출력 토큰 수로 계산합니다. 모델별 단가는 공식 요금 페이지에서 확인하세요.
Q. 어떤 모델을 사용할 수 있나요?
A. Llama, Mistral 계열 LLM, BGE 임베딩, Stable Diffusion, Whisper 등 여러 오픈 모델을 제공합니다. 모델 목록과 사용 중단 일정은 자주 바뀌므로 Cloudflare 문서의 모델 카탈로그를 참고하세요.
Q. OpenAI API vs Workers AI, 어떤 게 나은가요?
A. OpenAI API는 더 강력하지만 비쌉니다. Workers AI는 저렴하고 빠르지만 모델 선택이 제한적입니다. 간단한 작업은 Workers AI를 권장합니다.
Q. 한국에서도 빠른가요?
A. Worker 코드는 서울 등 국내 데이터센터에서 실행되어 네트워크 지연은 짧습니다. 다만 AI 추론은 GPU가 배치된 위치로 라우팅되고, LLM 응답 시간의 대부분은 토큰 생성 시간이라 “수십 ms 안에 답이 온다”고 기대하기는 어렵습니다. 체감 속도는 스트리밍 응답으로 개선하는 것이 현실적입니다.