از Fine-tune تا Deploy — مدلت رو سرو کن

قسمت ۱۰ ۲۲ دقیقه

مدل آموزش دیده — حالا چی؟

تا اینجا مدل رو Fine-tune کردی، ارزیابی کردی و از نتیجه راضی هستی. ولی یه مدل روی لپ‌تاپت فایده‌ای برای کسی نداره. باید سرو بشه — یعنی از طریق یه API قابل دسترس باشه.

توی این اپیزود، کل مسیر از ذخیره مدل تا deploy در محیط واقعی رو طی می‌کنیم.

فرمت‌های ذخیره مدل

۱. SafeTensors

فرمت استاندارد Hugging Face. امن، سریع و قابل اعتماد:

# ذخیره به فرمت safetensors (پیش‌فرض)
model.save_pretrained("./my-model", safe_serialization=True)
tokenizer.save_pretrained("./my-model")

# حجم تقریبی (8B model):
# FP16: ~16 GB
# FP32: ~32 GB

۲. GGUF — برای llama.cpp

GGUF فرمتیه که llama.cpp و Ollama ازش استفاده می‌کنن. مزیتش اینه که می‌تونی مدل رو quantize شده ذخیره کنی:

# ذخیره به فرمت GGUF با Unsloth
model.save_pretrained_gguf(
    "my-model-gguf",
    tokenizer,
    quantization_method="q4_k_m",  # 4-bit quantization
)

# quantization methods:
# "q4_k_m"  → ~4.5 GB (توصیه‌شده — تعادل کیفیت و حجم)
# "q5_k_m"  → ~5.5 GB (کیفیت بالاتر)
# "q8_0"    → ~8.5 GB (نزدیک به اصلی)
# "f16"     → ~16 GB (بدون quantization)

# یا با llama.cpp مستقیم:
# python convert_hf_to_gguf.py ./my-model --outtype q4_k_m

انتخاب فرمت مناسب

  • SafeTensors: برای سرو با vLLM، TGI، یا Hugging Face
  • GGUF: برای سرو با llama.cpp، Ollama، یا روی CPU

سرو با vLLM

vLLM سریع‌ترین و بهینه‌ترین ابزار برای سرو مدل‌های زبانیه. از PagedAttention استفاده می‌کنه و throughput خیلی بالایی داره:

# نصب
# pip install vllm

# راه‌اندازی سرور
# vllm serve ./my-model --port 8000

# یا در پایتون:
from vllm import LLM, SamplingParams

# لود مدل
llm = LLM(
    model="./my-model",
    tensor_parallel_size=1,    # تعداد GPU
    gpu_memory_utilization=0.9,
    max_model_len=4096,
)

# تنظیم پارامترهای تولید
sampling_params = SamplingParams(
    temperature=0.7,
    max_tokens=512,
    top_p=0.9,
)

# تولید جواب
prompts = ["سلام! من یه سوال دارم..."]
outputs = llm.generate(prompts, sampling_params)

for output in outputs:
    print(output.outputs[0].text)

سرور API با vLLM

# راه‌اندازی سرور OpenAI-compatible
# vllm serve ./my-model \
#   --host 0.0.0.0 \
#   --port 8000 \
#   --api-key my-secret-key

# استفاده از API (سازگار با OpenAI SDK)
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="my-secret-key",
)

response = client.chat.completions.create(
    model="./my-model",
    messages=[
        {"role": "system", "content": "تو یه دستیار هوشمند هستی."},
        {"role": "user", "content": "LoRA رو توضیح بده."},
    ],
    temperature=0.7,
    max_tokens=512,
)

print(response.choices[0].message.content)

ساخت API با FastAPI

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from vllm import LLM, SamplingParams
import uvicorn

app = FastAPI(title="My Fine-tuned Model API")

# لود مدل یک بار
llm = LLM(model="./my-model", gpu_memory_utilization=0.9)

class ChatRequest(BaseModel):
    messages: list[dict]
    temperature: float = 0.7
    max_tokens: int = 512

class ChatResponse(BaseModel):
    response: str
    tokens_used: int

@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
    try:
        # ساخت prompt از messages
        prompt = format_messages(request.messages)
        
        sampling_params = SamplingParams(
            temperature=request.temperature,
            max_tokens=request.max_tokens,
        )
        
        outputs = llm.generate([prompt], sampling_params)
        response_text = outputs[0].outputs[0].text
        
        return ChatResponse(
            response=response_text,
            tokens_used=len(outputs[0].outputs[0].token_ids),
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
async def health():
    return {"status": "healthy", "model": "my-fine-tuned-model"}

def format_messages(messages):
    """تبدیل messages به prompt"""
    formatted = ""
    for msg in messages:
        role = msg["role"]
        content = msg["content"]
        formatted += f"<|{role}|>\n{content}\n"
    formatted += "<|assistant|>\n"
    return formatted

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Deploy با Docker

# Dockerfile
"""
FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04

WORKDIR /app

# نصب Python و وابستگی‌ها
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip3 install vllm fastapi uvicorn

# کپی مدل و کد
COPY ./my-model /app/model
COPY ./server.py /app/server.py

# expose port
EXPOSE 8000

# اجرا
CMD ["python3", "server.py"]
"""

# docker-compose.yml
"""
version: '3.8'
services:
  model-server:
    build: .
    ports:
      - "8000:8000"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    volumes:
      - ./my-model:/app/model
    environment:
      - CUDA_VISIBLE_DEVICES=0
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
"""
# Build و اجرا
# docker compose build
# docker compose up -d

# بررسی وضعیت
# docker compose logs -f
# curl http://localhost:8000/health

Deploy با Ollama (ساده‌ترین روش)

# اگه مدل رو به فرمت GGUF تبدیل کردی:

# ۱. ساخت Modelfile
"""
# Modelfile
FROM ./my-model.Q4_K_M.gguf

PARAMETER temperature 0.7
PARAMETER num_ctx 4096

SYSTEM """تو یه دستیار هوشمند فارسی‌زبان هستی."""
"""

# ۲. ساخت مدل Ollama
# ollama create my-model -f Modelfile

# ۳. اجرا
# ollama run my-model

# ۴. API (خودکار)
# curl http://localhost:11434/api/chat -d '{
#   "model": "my-model",
#   "messages": [{"role": "user", "content": "سلام"}]
# }'

مانیتورینگ

import time
import logging
from collections import defaultdict

# ساده‌ترین شکل مانیتورینگ
class ModelMonitor:
    def __init__(self):
        self.metrics = defaultdict(list)
        self.logger = logging.getLogger("model_monitor")
    
    def log_request(self, prompt, response, latency, tokens):
        self.metrics["latency"].append(latency)
        self.metrics["tokens"].append(tokens)
        self.metrics["requests"].append(time.time())
        
        # هشدار برای latency بالا
        if latency > 5.0:
            self.logger.warning(f"High latency: {latency:.2f}s")
    
    def get_stats(self):
        latencies = self.metrics["latency"]
        if not latencies:
            return {}
        
        return {
            "total_requests": len(latencies),
            "avg_latency": sum(latencies) / len(latencies),
            "p95_latency": sorted(latencies)[int(len(latencies) * 0.95)],
            "avg_tokens": sum(self.metrics["tokens"]) / len(self.metrics["tokens"]),
        }

monitor = ModelMonitor()

# استفاده در API
@app.post("/chat")
async def chat(request: ChatRequest):
    start = time.time()
    
    # ... تولید جواب ...
    
    latency = time.time() - start
    monitor.log_request(
        prompt=request.messages[-1]["content"],
        response=response_text,
        latency=latency,
        tokens=len(token_ids),
    )
    
    return ChatResponse(response=response_text, tokens_used=len(token_ids))

@app.get("/metrics")
async def metrics():
    return monitor.get_stats()

بهینه‌سازی هزینه

# استراتژی‌های کاهش هزینه

# ۱. Quantization مدل — کاهش مصرف GPU
# GGUF Q4_K_M: ~4x کاهش حافظه با کیفیت خوب

# ۲. Batching — پردازش همزمان چند request
# vLLM خودکار continuous batching انجام می‌ده

# ۳. مدل کوچک‌تر — اگه کیفیت قابل قبوله
# 8B vs 70B: هزینه GPU تقریباً 10x کمتر

# ۴. Caching — ذخیره جواب‌های تکراری
from functools import lru_cache
import hashlib

response_cache = {}

def get_cached_response(prompt, temperature=0.0):
    """Cache فقط برای temperature=0 (قطعی)"""
    if temperature > 0:
        return None
    
    key = hashlib.md5(prompt.encode()).hexdigest()
    return response_cache.get(key)

def cache_response(prompt, response, temperature=0.0):
    if temperature == 0:
        key = hashlib.md5(prompt.encode()).hexdigest()
        response_cache[key] = response

# ۵. Auto-scaling — کم و زیاد کردن منابع بر اساس ترافیک
# با Kubernetes و GPU autoscaler

چک‌لیست Deploy

  • مدل در فرمت مناسب ذخیره شده (SafeTensors یا GGUF)
  • API endpoint کار می‌کنه و جواب درست می‌ده
  • Health check endpoint داری
  • مانیتورینگ فعاله (latency, error rate, throughput)
  • Rate limiting فعاله
  • Authentication/API key داری
  • Error handling درست پیاده شده
  • Docker image ساخته شده و تست شده
  • Backup از مدل و config داری

جمع‌بندی سری

تبریک! کل مسیر Fine-tuning رو طی کردی:

  • اپیزود ۱: فهمیدی Fine-tuning چیه و کِی لازمه
  • اپیزود ۲: سه مرحله آموزش مدل رو شناختی
  • اپیزود ۳: LoRA رو یاد گرفتی — Fine-tuning بهینه
  • اپیزود ۴: QLoRA — Fine-tuning با GPU معمولی
  • اپیزود ۵: آماده‌سازی دیتاست — مهم‌ترین بخش
  • اپیزود ۶: Unsloth — ابزار سریع و عملی
  • اپیزود ۷: ارزیابی مدل Fine-tune شده
  • اپیزود ۸: DPO — هم‌راستاسازی ساده
  • اپیزود ۹: چالش‌های زبان فارسی
  • اپیزود ۱۰: از Fine-tune تا Deploy

Fine-tuning یه مهارت عملیه. خوندن کافی نیست — باید دست به کد بشی. یه مدل کوچیک بگیر، یه دیتاست ساده بساز و شروع کن. اشتباه کردن بخشی از یادگیریه.

حالا دانش و ابزار لازم رو داری. برو و مدلت رو به سبک خودت در بیار!

نظرات

هنوز نظری ثبت نشده. اولین نفر باشید!

نظر خود را بنویسید