मैक डॉकर में क्रैश होने वाले लोकल एलएलएम (LLM) को ठीक करने के लिए इंजीनियरिंग सेटिंग्स
जब आप ओपन-सोर्स एआई वर्कस्पेस कोड डाउनलोड करके उसे मैक डॉकर पर चलाते हैं, तो आपको पहले ही प्रॉम्प्ट पर समस्याओं का सामना करना पड़ता है। हालांकि आप स्पष्ट रूप से एम-सीरीज़ चिप का उपयोग कर रहे हैं, लेकिन केवल पंखे तेज आवाज में चलते हैं और गति लगभग 10 टोकन प्रति सेकंड तक गिर जाती है। ऐसा इसलिए होता है क्योंकि Docker Desktop की वर्चुअलाइजेशन लेयर Apple Silicon GPU (Metal) को कंटेनर के अंदर पास नहीं कर पाती है, जिससे यह केवल CPU गणना पर निर्भर हो जाता है।
यदि आप इस बॉటిল넥 को ठीक किए बिना FastAPI बैकएंड और लोकल वेक्टर DB को एक साथ चलाते हैं, तो मेमोरी लीक और कनेक्शन समाप्त होने के कारण कंटेनर क्रैश हो जाएगा। केवल GitHub स्टार्स की संख्या देखकर डाउनलोड किए गए कोड को स्थानीय वातावरण में उत्पादन-स्तरीय बनाने के लिए आपको जिन चार सेटिंग्स को संशोधित करने की आवश्यकता है, वे नीचे दी गई हैं।
सीपीयू फॉールबैक के कारण और हार्डवेयर एक्सेलरेशन बायपास 경로
macOS पर Docker Desktop हल्के Linux VM पर चलता है। इसमें Linux गेस्ट OS कंटेनर के अंदर सीधे होस्ट के Metal GPU API को पास करने की कोई सुविधा नहीं है। कंटेनर के अंदर Ollama या llama.cpp VRAM एलोकेशन में विफल हो जाते हैं और चुपचाप CPU गणना पर स्विच हो जाते हैं। यही कारण है कि Llama 3 8B मॉडल, जो M3 Max पर नेटिव रूप से 40-80 t/s देता है, डॉकर के अंदर आते ही प्रॉम्प्ट इवैल्यूएशन के लिए 21.45 t/s और टोकन जनरेशन के लिए 12.17 t/s तक गिर जाता है।
`
[가상화 컨테이너 내 CPU 폴백 발생 메커니즘]
+-----------------------------------------------------------------------+
| Docker Container (Linux Guest OS) |
| +---------------------+ |
| | Local LLM Engine | --(VRAM Allocation Req)--> [VirtGPU Missing] |
| +---------------------+ | |
| | v |
| +<--(Fallback to CPU Execution)--- [Silent slog.Debug] |
+-------------|---------------------------------------------------------+
v
[Apple Silicon Host CPU (ARM Neon/DotProd)] -> 속도 저하 발생
`
डिफ़ॉल्ट शेयरड मेमोरी (shm) सेटिंग भी एक समस्या है। डिफ़ॉल्ट 64MB आवंटन बड़े पैमाने पर टेंसर गणना के दौरान तुरंत Bus Error का कारण बनता है। शेयरड मेमोरी का विस्तार करने और संसाधन सीमा हटाने के लिए ~/.docker/daemon.json खोलें।
`json
{
"builder": {
"gc": {
"defaultKeepStorage": "20GB",
"enabled": true
}
},
"experimental": false,
"default-shm-size": "8g"
}
`
`yaml
version: '3.8'
services:
llm-inference-engine:
image: ollama/ollama:latest
container_name: local_llm_engine
shm_size: '16gb'
ipc: host
deploy:
resources:
limits:
cpus: '8.0'
memory: 24G
reservations:
cpus: '4.0'
memory: 12G
ports:
- "11434:11434"
volumes:
- ollama_storage:/root/.ollama
volumes:
ollama_storage:
`
कंटेनर के अंदर से GPU एक्सेलरेशन प्राप्त करने के लिए, आपको libkrun वर्चुअल मशीन मॉनिटर और krunkit ड्राइवर का उपयोग करने वाले Podman पर स्विच करना होगा। यह कंटेनर के अंदर से Vulkan गणना अनुरोधों को होस्ट macOS के Metal API में पास करने की एक विधि है (Virtio-GPU Venus)। यह प्रोसेसिंग स्पीड को नेटिव Metal की तुलना में 75% तक बढ़ा देता है।
- Homebrew का उपयोग करके krunkit और Podman स्थापित करें।
brew tap slp/krunkit && brew install krunkit podman
- libkrun प्रदाता के आधार पर 8-कोर, 32GB RAM मशीन लॉन्च करें।
export CONTAINERS_MACHINE_PROVIDER="libkrun" && podman machine init --cpus 8 --memory 32768 && podman machine start
- जांचें कि ड्राइवर VM के अंदर माउंट हुआ है या नहीं।
podman machine ssh "ls -la /dev/dri"
`dockerfile
FROM fedora:40
RUN dnf -y install dnf-plugins-core &&
dnf -y copr enable slp/mesa-krunkit fedora-40-aarch64 &&
dnf -y install mesa-vulkan-drivers vulkan-loader vulkan-tools &&
dnf -y downgrade mesa-vulkan-drivers.aarch64 --repo copr:copr.fedorainfracloud.org:slp:mesa-krunkit &&
dnf clean all
ENV GGML_VULKAN=1
`
यदि बुनियादी ढांचा नियमों के कारण केवल सीपीयू गणना का उपयोग किया जाना चाहिए, तो ARMv8.4-A DotProduct और Neon वेक्टर निर्देशों के अनुरूप Q4_0_4_4 क्वांटाइजेशन प्रारूप का उपयोग करें। यह प्रॉम्प्ट इवैल्यूएशन स्पीड को 50.63 t/s तक बनाए रखता है, जिससे डिफ़ॉल्ट डॉकर सीपीयू निष्पादन की तुलना में प्रोसेसिंग समय आधा या उससे कम हो जाता है।
| 런타임 구성 방식 |
프롬프트 평가 (t/s) |
토큰 생성 (t/s) |
구축 난이도 |
특징 |
| Docker Desktop (기본 CPU) |
~21.45 |
~12.17 |
낮음 |
가상화 제약으로 CPU 폴백 |
| Docker Container (ARM Q4_0_4_4) |
~50.63 |
~14.01 |
보통 |
ARM Neon 벡터 명령어 활용 |
| Podman + libkrun (Vulkan Venus) |
Native 대비 ~75% |
Native 대비 ~75% |
높음 |
컨테이너 내부 Virtio-GPU 가속 |
| Host Native Metal + Container |
Native 100% (40~80) |
Native 100% |
보통 |
호스트 엔진 직접 호출 구조 |
FastAPI 메모리 누수 추적과 끊긴 스트림 회수
ओपन-सोर्स बैकएंड में अक्सर异步 (असिमक्रोनस) टास्क स्टेट मैनेजमेंट या CPython गार्बेज कलेक्टर की खराब हैंडलिंग होती है। जैसे-जैसे अनुरोध जमा होते जाते हैं, मेमोरी बढ़ती जाती है और अंततः प्रक्रिया क्रैश हो जाती है। tracemalloc का उपयोग करके मेमोरी खपत करने वाले बिंदुओं का पता लगाएं।
`python
import gc
import tracemalloc
tracemalloc.start()
def log_memory_snapshot():
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[Memory Debug] Top 5 Allocations:")
for stat in top_stats[:5]:
print(stat)
gc.collect()
`
प्रक्रिया प्रबंधन चरण के दौरान मेमोरी को जबरري रूप से साफ़ किया जाना चाहिए। Gunicorn वर्कर को हर 1000 अनुरोधों को प्रोसेस करने के बाद स्वचालित रूप से मेमोरी वापस करने और पुनरारंभ करने के लिए एक जीवनचक्र सेट करें।
`bash
gunicorn
--workers 4
--worker-class uvicorn.workers.UvicornWorker
--bind 0.0.0.0:8000
--max-requests 1000
--max-requests-jitter 100
--timeout 120
--keep-alive 5
--preload-app
app.main:app
`
--max-requests-jitter 100 जोड़कर, आप सभी 4 वर्करों को एक साथ मरने और पुनर्जीवित होने से रोकते हैं जिससे अनुरोध छूट सकते हैं, और अनुत्तरदायी कार्यों को 120 सेकंड के टाइमआउट के साथ काट दिया जाता है।
यदि उपयोगकर्ता उत्तर के बीच में ब्राउज़र बंद कर देता है और असिमक्रोनस जनरेटर लूप चलाना जारी रखता है, तो कंप्यूटिंग संसाधन बर्बाद हो जाते हैं। यह जांचने के लिए कि कनेक्शन टूट गया है या नहीं, request.is_disconnected() का उपयोग करें और तुरंत लूप से बाहर निकलें।
`python
import asyncio
import gc
import logging
from typing import AsyncGenerator
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse
app = FastAPI(title="Production AI Workspace Backend")
logger = logging.getLogger("stream_logger")
async def robust_llm_token_stream(prompt: str, request: Request) -> AsyncGenerator[str, None]:
try:
for token_idx in range(2000):
if await request.is_disconnected():
logger.warning(f"[Stream Aborted] Client disconnected at step {token_idx}.")
break
await asyncio.sleep(0.01)
yield f"event: message\ndata: {{"id": {token_idx}, "text": "chunk_{token_idx} "}}\n\n"
except asyncio.CancelledError:
logger.info("[Stream Cancelled] Task cancelled by ASGI server.")
raise
except Exception as err:
logger.error(f"[Stream Error] Error during streaming: {str(err)}")
raise
finally:
logger.info("[Stream Cleanup] Releasing context and triggering GC.")
gc.collect()
@app.post("/api/v1/chat/stream")
async def chat_stream_endpoint(request: Request, body: dict):
prompt = body.get("prompt", "")
if not prompt:
raise HTTPException(status_code=400, detail="Prompt string is missing.")
return StreamingResponse(
robust_llm_token_stream(prompt, request),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no"
}
)
`
의존성 고정과 오프라인 빌드 파이프라인
अपस्ट्रीम में एक भी मामूली अपडेट से लोकल कंटेनर बिल्ड का टूटना आम बात है। SHA-256 हैश वाले requirements.txt का उपयोग करके पैकेज में फेरबदल और संस्करण संघर्षों को रोकें।
`plaintext
fastapi==0.110.0 --hash=sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
uvicorn==0.28.0 --hash=sha256:2c6a81387e0037a34ca6a7f8087796030c79eb299f116515822ee483c6604e43
pydantic==2.6.4 --hash=sha256:d82e212f451f2d6c19f5a5e3a89369322e70e1781297587786411516279f64a5
sqlalchemy==2.0.28 --hash=sha256:a611116c2bb45f8f3077e6822ec3a37b384ff6b9a84d4dd88a0e8eb876b5cf12
`
ऑफ़लाइन वातावरण जैसे कि कॉर्पोरेट सुरक्षा नेटवर्क या विमान के भीतर डॉकर को पुनർनिर्माण करते समय pip डाउनलोड विफलताओं को रोकने के लिए व्हील बाइनरी को स्थानीय निर्देशिका में पहले से डाउनलोड करें।
`bash
pip wheel --wheel-dir=./wheels_repository -r requirements.txt
`
`dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY ./wheels_repository /app/wheels_repository
COPY requirements.txt .
RUN pip install --no-cache-dir --no-index --find-links=/app/wheels_repository -r requirements.txt
COPY . .
CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-c", "gunicorn.conf.py", "app.main:app"]
`
अपस्ट्रीम Git रिपॉजिटरी परिवर्तनों को पूरी तरह से pull करने से आपके द्वारा सावधानीपूर्वक ट्यून किए गए स्थानीय सेटिंग्स नष्ट हो सकते हैं। ऑप्टिमाइजेशन ब्रांच को अलग करें और केवल आवश्यक कमिट को चेरी-पिक करें।
`bash
git remote add upstream https://github.com/opensource-ai-workspace/workspace.git
git fetch upstream
git checkout -b feature/local-mac-optimization
git cherry-pick
`
DB 커넥션 병목 제어와 격리 네트워크 라우팅
जब मल्टी-एजेंट एक ही बार में एम्बेडिंग सर्च, मेमोरी लुकअप और चैट लॉगिंग करते हैं, तो PostgreSQL कनेक्शन पूल तुरंत समाप्त हो जाता है। 8-कोर Apple Silicon सिंगल-डिस्क वातावरण के आधार पर कनेक्शन गणना सूत्र (Nextconn=extCPU코어imes2+ext스핀들수) के अनुसार इंजन पूल को 17 से कम तक सीमित करें।
`python
from sqlalchemy.ext.asyncio import create_async_engine
DATABASE_URL = "postgresql+asyncpg://postgres:password@localhost:6432/ai_workspace"
engine = create_async_engine(
DATABASE_URL,
pool_size=15,
max_overflow=5,
pool_timeout=10,
pool_recycle=300,
pool_pre_ping=True
)
`
एजेंट द्वारा LLM अनुमान परिणामों की प्रतीक्षा करते समय DB सत्र को पकड़े रहने की समस्या को PgBouncer ट्रांजेक्शन पूलिंग के साथ काटा जाता है।
`ini
[databases]
ai_workspace = host=postgres_db port=5432 dbname=ai_workspace auth_user=postgres
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = plain
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
idle_transaction_timeout = 60
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
`
`
+-------------------------------------------------------------------------------+
| Docker Internal Isolated Network (internal: true) |
| |
| +--------------------+ +--------------------+ +-----------------+ |
| | AI Workspace App | ---> | PgBouncer Proxy | ---> | PostgreSQL DB | |
| | (FastAPI Backend) | | (Port 6432) | | (Port 5432) | |
| +--------------------+ +--------------------+ +-----------------+ |
| | | |
| | [pool_mode = transaction] |
| | [idle_tx_timeout = 60s] |
| v |
| +--------------------+ |
| | Local Vector DB | (외부 인터넷 통신 차단) |
| | (Qdrant) | |
| +--------------------+ |
+-------------------------------------------------------------------------------+
`
데이터 유출 위험을 없애기 위해 컨테이너 네트워크에 internal: true를 걸어 외부 클라우드 통신을 막습니다. 호스트 머신에서 직접 구동 중인 고속 Metal Ollama 런타임과는 host.docker.internal 게이트웨이로만 연결합니다.
`yaml
version: '3.8'
services:
backend-app:
build: .
environment:
- DB_HOST=pgbouncer
- DB_PORT=6432
- VECTOR_DB_HOST=vector-db
- OLLAMA_HOST=http://host.docker.internal:11434
networks:
- isolated_local_net
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "8000:8000"
pgbouncer:
image: edoburu/pgbouncer:latest
environment:
- DB_HOST=postgres_db
- DB_PORT=5432
- DB_USER=postgres
- DB_PASSWORD=secret
- POOL_MODE=transaction
networks:
- isolated_local_net
depends_on:
- postgres_db
postgres_db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=ai_workspace
- POSTGRES_PASSWORD=secret
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- isolated_local_net
vector-db:
image: qdrant/qdrant:v1.9.0
volumes:
- qdrant_data:/qdrant/storage
networks:
- isolated_local_net
networks:
isolated_local_net:
driver: bridge
internal: true
volumes:
pgdata:
qdrant_data:
`
GPU 가상화 병목을 Podman이나 호스트 브리지로 우회하고, 워커 재활용과 DB 프록시로 수명 주기를 제어하면 M 시리즈 칩이 달린 랩톱에서도 뻗지 않는 독립 개발 환경이 완성됩니다.