AI 모델을 한 번 돌릴 때마다 1~3분씩 하염없이 기다리는 시간, 정말 지루하죠. 특히 이것저것 프로젝트를 많이 벌여놓고 테스트하다 보면 로딩 속도 때문에 흐름이 다 끊기기 일쑤입니다. 게다가 여러 파이썬 프로그램에서 같은 로컬 LLM을 동시에 불러오려다 메모리 부족(OOM) 메시지를 볼 때면 정말 힘이 빠지죠.
이런 고민을 하시는 분들께 해결책이 있습니다. 바로 FastAPI를 활용한 로컬 AI 서버 구축입니다.
왜 FastAPI로 서버를 구축해야 할까?
가장 큰 이유는 ‘모델 메모리 점유의 효율화’입니다.
기존 방식처럼 파이썬 프로그램마다 모델을 각각 로드하면, 프로젝트 개수만큼 메모리가 낭비됩니다. 하지만 서버를 하나 띄워두고 모델을 메모리에 상주시키면, 여러 프로그램이 API 호출을 통해 모델을 공유할 수 있게 됩니다.
이번 글에서는 llama-server를 FastAPI로 감싸 마이크로서비스 아키텍처(Microservice Architecture)로 전환하는 방법을 초기 설정부터 외부 연결까지 다룹니다. 내부 네트워크 공유부터 외부 인터넷 접속, 보안 설정, DDNS 도메인 고정까지 실무에 바로 적용 가능한 전체 구현 과정을 단계별로 설명합니다.
이 글의 주제:
- 로컬 AI 서버를 직접 실행 방식에서 API 서버 방식으로 전환하는 이유와 방법
- FastAPI로 llama-server를 래핑하는 ai_server.py 구현
- 모든 프로그램에서 재사용 가능한 ai_client.py 공통 모듈 설계
- Python, JavaScript, cURL 등 다양한 클라이언트에서 호출하는 방법
- 외부 접속, 보안(API 키·HTTPS·IP 제한), DDNS 설정까지 완전한 운영 환경 구성
목차
1. 일반적인 AI 구동 방식의 구조적 한계
로컬 LLM을 Python 프로그램에 직접 연결하는 방식은 가장 단순하지만, 실제 운영 환경에서 심각한 병목을 만들어냅니다.
기존 구조 흐름
main.py (GUI 프로그램)
↓
vision_engine.py (직접 llama-server 실행)
↓
llama-server.exe (GPU 메모리에 모델 로드)기존 방식에서는 main.py가 시작될 때 vision_engine.py를 통해 llama-server.exe를 직접 실행합니다. 언뜻 보면 간단해 보이지만, 이 구조는 세 가지 치명적인 문제를 내포하고 있습니다.
문제 1: 프로그램 실행마다 AI 로딩 대기
매번 llama-server를 실행하면서 AI 모델 가중치 파일 전체를 GPU VRAM에 올리는 과정입니다. 모델 크기에 따라 다르지만 일반적으로 7B 모델 기준 1~3분, 13B 이상이면 그 이상의 시간이 소요됩니다. 프로그램을 재시작할 때마다 이 과정이 반복됩니다.
문제 2: 여러 프로그램 동시 사용 불가
GPU VRAM은 프로세스 간에 자동으로 공유되지 않습니다. 서로 다른 프로그램이 각각 동일한 모델을 로드하려 하면, 동일한 모델 가중치가 VRAM에 두 번 올라갑니다. 8GB VRAM 환경에서 4GB 모델을 두 프로그램이 각각 로드하면 즉시 VRAM 부족으로 실행 자체가 불가능해집니다.
문제 3: 메모리 낭비와 구조적 결합
모델 관리 로직이 각 프로그램 내부에 분산되어 있어, 모델 버전을 교체하거나 파라미터를 변경할 때 모든 프로그램을 각각 수정해야 합니다. 이는 유지보수 비용을 기하급수적으로 높입니다.
2. 마이크로서비스 아키텍처로의 전환: 로컬 AI 서버 구조
마이크로서비스 아키텍처(Microservice Architecture)는 독립적으로 배포 가능한 소규모 서비스로 애플리케이션을 분리하는 설계 철학입니다. 로컬 AI 서버에 이 개념을 적용하면, AI 모델 제공 기능을 하나의 독립 서비스로 분리하고 모든 클라이언트가 API를 통해 접근하게 됩니다.
구조 도식
ai_server.py (FastAPI 서버)
vision_engine.py → llama-server.exe (GPU)
FastAPI 포트: 8000
↓ HTTP API 호출
main.py
ai_client.py → ai_server.py로 요청
↓
다른 프로그램 ( 예: 인보이스, 챗봇, etc.)
ai_client.py로 ai_server.py에 요청
AI 서버를 구축시 비교
| 항목 | 기존 방식 (직접 로드) | AI 서버 방식 (API 호출) |
|---|---|---|
| 모델 로드 시점 | 프로그램 시작마다 | 서버 시작 시 1회만 |
| 대기 시간 | 1~3분 (매번) | 0초 (이미 로드됨) |
| VRAM 사용 | 프로그램마다 중복 | 1개 모델만 사용 |
| 여러 프로그램 공유 | 불가능 | 가능 |
| 코드 복잡성 | 복잡 (모델 직접 관리) | 간단 (API 호출만) |
| 다른 언어 지원 | Python만 가능 | 모든 언어 가능 |
3. AI 서버 방식이 반드시 필요한 4가지 이유
이유 1: 하나의 AI 모델을 여러 프로그램이 공유
로컬 LLM 서버를 구축하면 단일 모델 인스턴스를 여러 애플리케이션이 동시에 사용할 수 있습니다.
예:
- 상담사 프로그램 (
system_prompt: “당신은 전문 상담사입니다.”) - 인보이스 생성 프로그램 (
system_prompt: “당신은 회계 전문가입니다.”) - 일반 챗봇, 문서 요약 프로그램 등
이 모든 클라이언트가 동일한 ai_server.py에 API 요청을 보내며, 각자 다른 system_prompt를 설정해 동일 모델로 완전히 다른 역할을 수행하게 할 수 있습니다.
이유 2: 모델 로드 대기 시간 완전 제거
서버를 한 번 실행해 모델을 VRAM에 올려두면, 이후 모든 클라이언트 프로그램은 대기 없이 즉시 응답을 받을 수 있습니다. 서버가 실행 중인 상태에서는 python main.py를 실행하는 순간 곧바로 AI 응답이 가능합니다.
이유 3: GPU VRAM 효율적 사용
동일한 모델을 여러 프로세스가 각각 로드하는 대신, 하나의 프로세스(ai_server.py)가 모델을 VRAM에 올린 뒤 모든 요청을 처리합니다. 이 방식은 VRAM 사용량을 최소화하면서 다중 클라이언트 서비스를 가능하게 합니다.
이유 4: 서버-클라이언트 독립적 업데이트
AI 서버를 그대로 두고 클라이언트(main.py)만 수정하거나, 반대로 클라이언트를 건드리지 않고 서버 측 모델이나 파라미터만 교체할 수 있습니다. 관심사의 분리(Separation of Concerns) 원칙에 따라 각 컴포넌트가 독립적으로 진화할 수 있는 구조입니다.
4. 핵심 파일 구성 및 역할
현재 구성된 파일들의 위치와 역할은 다음과 같습니다.
| 파일 | 위치 | 역할 |
|---|---|---|
ai_server.py | G:\AI_Study\AI_server | FastAPI 서버 (AI 모델 제공, 단 한 번만 실행) |
| G:\AI_Study\AI_server | llama-server 실행 및 AI 응답 생성 (서버 내부) |
main.py | G:\MAIN_project | AI GUI 프로그램 (클라이언트) |
ai_client.py | 공유 모듈 | 모든 프로그램에서 재사용 가능한 클라이언트 클래스 |
MAIN.bat | G:\MAIN | 서버 + 클라이언트 한 번에 실행하는 배치 파일 |
이 구조에서 ai_server.py와 vision_engine.py는 AI 서비스 레이어, main.py와 각 프로그램은 클라이언트 레이어로 명확히 분리됩니다.
5. 단계별 구현: FastAPI AI 서버 코드 (ai_server.py)
로컬 LLM API 서버의 핵심은 ai_server.py입니다. FastAPI는 Python 기반 고성능 비동기 웹 프레임워크로, 코드 작성량을 최소화하면서 자동 API 문서(/docs)까지 생성해줍니다.
# ai_server.py - AI 서버 (한 번만 실행)
from fastapi import FastAPI
from vision_engine import VisionEngine
# 서버 시작 시 AI 엔진 한 번만 로드 (1~3분 소요, 이후 0초)
engine = VisionEngine()
engine.load_model()
app = FastAPI()
@app.post("/chat")
async def chat(request: ChatRequest):
# API 요청이 오면 이미 로드된 엔진으로 즉시 응답 생성
response = engine.generate_reply(request.message)
return {"response": response}vision_engine.py를 통해 llama-server.exe가 실행되고, GPU에 모델이 로드됩니다. 이 초기화는 서버 시작 시 딱 한 번만 수행되며, 이후 들어오는 모든 /chat 요청은 이미 VRAM에 올라간 모델을 즉시 활용합니다.
FastAPI 팁: 서버 실행 후 브라우저에서
http://localhost:8000/docs에 접속하면 Swagger UI로 자동 생성된 API 문서를 확인하고 직접 테스트할 수 있습니다.
6. 단계별 구현: 클라이언트 코드 (main.py)
클라이언트 프로그램은 AI 모델 로드 로직을 완전히 제거하고, HTTP 요청만 담당합니다.
# main.py - AI 서버에 API 요청 (모델 로드 없음)
import requests
class BuddhaChatApp:
def __init__(self):
# AI 서버 URL만 설정 (모델 로드 없음 → 즉시 시작)
self.ai_server_url = "http://localhost:8000"
def process_message(self, user_input):
# AI 서버에 POST 요청 → 즉시 응답 반환
response = requests.post(
f"{self.ai_server_url}/chat",
json={"message": user_input}
)
return response.json()["response"]기존 방식과 비교하면 VisionEngine 임포트, load_model() 호출이 모두 사라졌습니다. requests.post() 한 줄로 AI 기능 전체를 활용할 수 있습니다. 프로그램 시작 시간은 AI 모델 로드 시간이 아닌 GUI 초기화 시간에만 의존하게 됩니다.
7. 공통 모듈 ai_client.py로 코드 재사용성 극대화
여러 프로그램에서 AI 서버를 호출할 때 매번 requests.post() 로직을 반복 작성하는 것은 비효율적입니다. ai_client.py 공통 모듈을 만들어 모든 프로그램에서 재사용하면 코드 중복을 제거하고 유지보수 지점을 단일화할 수 있습니다.
# ai_client.py - 모든 프로그램에서 재사용 가능한 AI 클라이언트 모듈
import requests
class AIClient:
"""AI 서버 클라이언트 (모든 Python 프로그램에서 재사용)"""
def __init__(self, server_url="http://localhost:8000"):
self.server_url = server_url
def chat(self, message, system_prompt=None):
"""AI에게 질문 — system_prompt로 역할 지정 가능"""
response = requests.post(
f"{self.server_url}/chat",
json={
"message": message,
"system_prompt": system_prompt
},
timeout=180 # LLM 응답 지연 고려한 타임아웃
)
return response.json()["response"]
def health_check(self):
"""서버 상태 확인 — 연결 전 헬스체크용"""
response = requests.get(f"{self.server_url}/health")
return response.json()사용 예시 — 상담사:
from ai_client import AIClient
client = AIClient()
response = client.chat(
"삶의 의미가 무엇인가요?",
system_prompt="당신은 전문 상담사입니다."
)사용 예시 — 인보이스 프로그램:
from ai_client import AIClient
client = AIClient()
response = client.chat(
"인보이스 번호 12345의 상태를 알려줘",
system_prompt="당신은 회계 전문가입니다."
)AIClient 클래스 하나로 어떤 프로그램에서도 동일한 인터페이스로 AI 서버를 호출할 수 있습니다. 서버 URL이 바뀌거나 인증 방식이 변경되더라도 ai_client.py 한 곳만 수정하면 됩니다.
8. 외부에서 로컬 AI 서버에 접속하는 방법
로컬 AI 서버를 같은 PC나 LAN 환경을 넘어 외부 인터넷에서 접속 가능하게 만들려면 세 가지 설정이 필요합니다.
8-1. 서버 바인딩 주소 변경 (0.0.0.0)
기본값인 127.0.0.1(localhost)은 자기 자신만 접속 가능합니다. 외부 접속을 허용하려면 0.0.0.0으로 변경해 모든 네트워크 인터페이스에서 수신하도록 설정합니다.
# ai_server.py 하단 실행 설정
import os
import uvicorn
if __name__ == "__main__":
port = int(os.environ.get("AI_SERVER_PORT", 8000))
# 0.0.0.0으로 변경 → 외부(같은 네트워크 포함) 접속 허용
uvicorn.run(
"ai_server:app",
host="0.0.0.0", # 모든 IP에서 접속 허용
port=port,
workers=1, # GPU 직렬 처리를 위해 단일 워커 권장
log_level="info",
)workers=1 이유: GPU 기반 LLM 추론은 병렬 처리보다 직렬(순차) 처리가 더 안정적입니다. 멀티 워커를 사용하면 GPU 메모리 경합이 발생할 수 있으므로,
workers=1로 단일 워커에서 요청을 순차 처리하는 것을 권장합니다.
8-2. 내 IP 주소 확인
외부에서 접속할 IP를 확인합니다.
# Windows에서 내부 IP 확인
ipconfig
# 확인할 항목
IPv4 주소: 192.168.0.xxx (LAN 내부 IP)외부(인터넷) IP를 확인하려면:
# 명령어로 공인 IP 확인
curl ifconfig.me
# 또는 구글에서 "내 IP 주소" 검색8-3. Windows 방화벽 포트 허용
외부에서 8000번 포트로 들어오는 연결을 허용합니다. 관리자 권한으로 명령 프롬프트를 실행하여 아래 명령어를 입력합니다.
netsh advfirewall firewall add rule name="AI Server" dir=in action=allow protocol=TCP localport=80008-4. 공유기 포트 포워딩 설정
같은 LAN이 아닌 인터넷 외부에서 접속하려면 공유기에서 포트 포워딩(NAT 설정)을 추가해야 합니다. 공유기 관리 페이지(보통 192.168.0.1 또는 192.168.1.1)에 접속해 다음과 같이 설정합니다.
| 설정 항목 | 값 |
|---|---|
| 외부 포트 | 8000 (또는 원하는 포트) |
| 내부 IP | 192.168.0.xxx (내 컴퓨터 IP) |
| 내부 포트 | 8000 |
| 프로토콜 | TCP |
8-5. 전체 외부 접속 구조
외부 사용자 (인터넷)
↓
공유기 (포트 포워딩: 외부 8000 → 내부 192.168.0.xxx:8000)
↓
내 컴퓨터 (192.168.0.xxx:8000)
↓
ai_server.py (FastAPI)
↓
vision_engine.py (llama-server)
↓
GPU (AI 모델 VRAM에 상주)9. 다양한 프로그램 언어와 환경에서 AI 서버 호출하기
FastAPI 기반 로컬 LLM 서버는 HTTP API를 제공하므로 Python뿐 아니라 모든 언어에서 호출 가능합니다.
9-1. Python 클라이언트 (외부 컴퓨터에서)
# 외부 컴퓨터에서 실행
import requests
class AIClient:
def __init__(self, server_url="http://192.168.0.xxx:8000"):
self.server_url = server_url
def chat(self, message):
response = requests.post(
f"{self.server_url}/chat",
json={"message": message},
timeout=180
)
return response.json()["response"]
# 사용
client = AIClient()
result = client.chat("안녕하세요")
print(result)9-2. JavaScript 클라이언트 (웹 브라우저에서)
// 웹 브라우저 또는 Node.js에서 호출
async function chatWithAI(message) {
const response = await fetch('http://192.168.0.xxx:8000/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: message })
});
const data = await response.json();
return data.response;
}
// 사용
chatWithAI("안녕하세요").then(result => console.log(result));9-3. cURL (터미널·테스트용)
curl -X POST http://192.168.0.xxx:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "안녕하세요"}'PHP, Ruby, Go, Rust 등 HTTP 요청이 가능한 모든 언어에서 동일한 방식으로 호출할 수 있습니다. 이는 AI 서버를 내부 API 서버처럼 운영할 수 있다는 것을 의미하며, 웹 서비스나 모바일 앱 백엔드와의 연동도 가능해집니다.
10. 보안 강화: 외부 접속 시 필수 설정
로컬 AI 서버를 외부에 노출할 때 보안 설정을 생략하면 누구나 무제한으로 API를 호출할 수 있습니다. 다음 세 가지 보안 레이어를 반드시 적용하세요.
10-1. API 키 인증 추가
모든 요청에 API 키 헤더를 요구하도록 FastAPI에 검증 미들웨어를 추가합니다.
# ai_server.py - API 키 검증 추가
from fastapi import HTTPException, Header
API_KEY = "your-secret-key-here" # 실제 운영 시 환경변수로 관리 권장
@app.post("/chat")
async def chat(
request: ChatRequest,
x_api_key: str = Header(...) # 헤더에서 API 키 확인
):
if x_api_key != API_KEY:
raise HTTPException(status_code=401, detail="Invalid API Key")
# ... 나머지 처리 코드클라이언트에서는 요청 헤더에 API 키를 포함합니다.
# 클라이언트 - API 키 포함 요청
headers = {"x-api-key": "your-secret-key-here"}
response = requests.post(
f"{server_url}/chat",
json={"message": message},
headers=headers
)보안 팁: API 키를 소스 코드에 하드코딩하지 말고 환경변수로 관리하세요.
python-dotenv라이브러리를 사용하면.env파일에API_KEY=your-secret-key를 저장하고os.environ.get("API_KEY")로 불러올 수 있습니다. 이 방식을 사용하면 키가 소스 코드 저장소에 노출되지 않습니다.
10-2. HTTPS 설정 (SSL 인증서)
HTTP 통신은 데이터가 평문으로 전송되므로, 외부 접속 시 HTTPS로 암호화하는 것이 강력히 권장됩니다. Let’s Encrypt 등에서 발급한 SSL 인증서를 적용합니다.
# ai_server.py - HTTPS 실행
uvicorn.run(
"ai_server:app",
host="0.0.0.0",
port=8000,
ssl_keyfile="./key.pem", # 개인키 파일 경로
ssl_certfile="./cert.pem" # 인증서 파일 경로
)10-3. 특정 IP만 접속 허용
신뢰할 수 있는 IP 주소 목록을 관리하고, 목록에 없는 IP의 접속을 차단합니다.
# ai_server.py - IP 화이트리스트 미들웨어
from fastapi.responses import JSONResponse
ALLOWED_IPS = ["192.168.0.100", "123.45.67.89"]
@app.middleware("http")
async def check_ip(request, call_next):
client_ip = request.client.host
if client_ip not in ALLOWED_IPS:
return JSONResponse(
status_code=403,
content={"error": "Access denied"}
)
return await call_next(request)보안 레이어의 적용 우선순위: API 키 인증 → IP 제한 → HTTPS 순으로 적용하면 외부 노출 위험을 최소화할 수 있습니다.
11. DDNS로 고정 도메인 설정하기 (DuckDNS)
가정용 인터넷 회선은 공인 IP가 주기적으로 변경(동적 IP)됩니다. 서버 IP가 바뀔 때마다 모든 클라이언트의 URL을 업데이트하는 것은 비현실적입니다. DDNS(Dynamic DNS) 서비스를 사용하면 동적 IP를 고정된 도메인 주소로 접근할 수 있습니다.
11-1. DuckDNS 설정 (무료)
- https://www.duckdns.org 에서 회원가입
- 도메인 생성 (예:
myaiserver.duckdns.org) - 현재 내 공인 IP로 도메인 업데이트
11-2. Python으로 DDNS 자동 업데이트
서버 시작 시 자동으로 IP를 갱신하도록 설정합니다.
import requests
def update_ddns():
"""DuckDNS 도메인을 현재 IP로 자동 업데이트"""
url = "https://www.duckdns.org/update"
params = {
"domains": "myaiserver", # 생성한 도메인 이름
"token": "your-duckdns-token", # DuckDNS 토큰
"ip": "" # 비워두면 자동으로 현재 IP 사용
}
requests.get(url, params=params)
# ai_server.py 시작 시 실행
update_ddns()11-3. 고정 도메인으로 클라이언트 연결
# DDNS 설정 후 — 도메인으로 안정적으로 연결
client = AIClient(server_url="http://myaiserver.duckdns.org:8000")이제 서버의 공인 IP가 바뀌어도 myaiserver.duckdns.org라는 주소는 항상 최신 IP를 가리키므로, 클라이언트 코드를 수정할 필요가 없습니다.
12. 실전 실행 방법: 처음부터 끝까지
실행 방법 1: 배치 파일로 한 번에 실행
ai.bat배치 파일이 서버를 먼저 실행하고, 서버가 준비되면 클라이언트 프로그램을 자동으로 실행합니다.
실행 방법 2: 서버만 단독 실행
cd G:\AI_Study\AI_server
conda activate ai_venv
python ai_server.py서버를 24시간 상주시키고 여러 클라이언트가 접속하는 방식으로 운영할 때 사용합니다.
실행 방법 3: 클라이언트만 실행 (서버 실행 중일 때)
cd G:\main_project
python main.py서버가 이미 실행 중이라면 클라이언트는 즉시 시작됩니다. 모델 로드 대기 시간이 없습니다.
기존 방식과 AI 서버 방식 실행 순서 비교
기존 방식:
python main.py # 실행 → 모델 로드 (1~3분) → 사용 가능AI 서버 방식:
# 1. 서버 실행 (처음 한 번만)
python ai_server.py # 모델 로드 (1~3분)
# 2. 이후 클라이언트는 몇 번이든 즉시 실행
python main.py # 즉시 실행 (대기 없음)
python invoice_app.py # 즉시 실행 (대기 없음)
python chatbot.py # 즉시 실행 (대기 없음)외부 접속 시작 5단계 체크리스트
# 1단계: 서버 실행 (내 컴퓨터)
python ai_server.py
# 2단계: 내부 IP 확인
ipconfig # IPv4 주소 항목 확인
# 3단계: 방화벽 포트 허용 (관리자 권한 명령 프롬프트)
netsh advfirewall firewall add rule name="AI Server" dir=in action=allow protocol=TCP localport=8000
# 4단계: 공유기 포트포워딩 설정 (필요 시)
# 공유기 관리 페이지 → 포트포워딩 → 외부 8000 → 내부 192.168.0.xxx:8000
# 5단계: 외부에서 접속 테스트
curl http://192.168.0.xxx:8000/health주의사항
| 항목 | 주의사항 |
|---|---|
| 보안 | 외부 공개 시 반드시 API 키 또는 IP 제한 설정 |
| 속도 | 외부 접속 응답 속도는 서버 컴퓨터의 인터넷 업로드 속도에 영향 |
| 전력 | 24시간 서버 운영 시 GPU 포함 전기 요금 고려 필요 |
| 방화벽 | Windows 방화벽에서 8000번 포트 인바운드 허용 필수 |
| 공유기 | 외부 인터넷 접속 시 포트 포워딩(NAT) 설정 필요 |
| 동적 IP | 가정용 회선은 IP 변경 가능 → DDNS 적용 권장 |
로컬 AI 서버 구축으로 얻는 것
FastAPI + llama-server 기반 로컬 LLM 마이크로서비스 서버를 구축하면 기존의 직접 로드 방식이 가진 모든 한계를 한 번에 극복할 수 있습니다.
최종 비교 요약
| 항목 | 기존 방식 | AI 서버 방식 |
|---|---|---|
| 모델 로드 | 프로그램 시작마다 반복 | 서버 시작 시 1회만 |
| 대기 시간 | 1~3분 (매번) | 0초 (이미 로드됨) |
| 메모리 사용 | 프로그램마다 중복 | 1개 모델만 공유 |
| 여러 프로그램 | 불가능 | 가능 (무제한 공유) |
| 코드 복잡성 | 복잡 (모델 직접 관리) | 간단 (API 호출만) |
| 다른 언어 지원 | Python만 가능 | 모든 언어 가능 |
AI 모델 관리라는 단일 책임을 서버에 위임하고, 각 클라이언트는 비즈니스 로직에만 집중하는 이 구조는 로컬 AI 서버 운영의 사실상 표준 패턴입니다. 처음에는 설정이 조금 복잡해 보일 수 있지만, 두 번째 AI 프로그램을 추가하는 순간 이 구조의 가치를 실감하게 됩니다.


