ในโลกของ AI ที่ต้องการความเร็วและประสิทธิภาพ การจัดการ GPU Resource อย่างเหมาะสมคือหัวใจสำคัญที่แยกระบบที่ล่มสลายออกจากระบบที่ทำงานได้อย่างราบรื่น ในบทความนี้ผมจะพาทุกท่านไปทำความเข้าใจ GPU Allocation ตั้งแต่พื้นฐานจนถึง Advanced Techniques พร้อมกรณีศึกษาจริงจากการทำงาน

กรณีศึกษา: ระบบ AI ลูกค้าสัมพันธ์อีคอมเมิร์ซที่รองรับ Flash Sale 10,000 Requests/วินาที

สมมติว่าทีมของเราต้องสร้างระบบ AI Chatbot สำหรับแพลตฟอร์มอีคอมเมิร์ซยักษ์ใหญ่แห่งหนึ่งในเอเชียตะวันออกเฉียงใต้ โจทย์คือระบบต้องรองรับ Flash Sale ที่มีคำสั่งซื้อพุ่งสูงผิดปกติ 10,000 requests ต่อวินาที พร้อมกับตอบคำถามลูกค้าแบบ Real-time โดยใช้งบประมาณจำกัดและต้องรองรับช่วง Off-peak ที่มี Traffic เพียง 100 requests/วินาที

ปัญหาที่เจอคือ GPU 1 ตัวไม่เพียงพอสำหรับ Peak แต่ถ้าใช้ GPU หลายตัวตลอดเวลาจะสิ้นเปลืองมาก วิธีแก้คือ Dynamic GPU Allocation ที่ปรับ Resource ตาม Load จริง ซึ่งเป็นหัวใจหลักของบทความนี้

GPU Memory Architecture และการทำงานของ Model Serving

ก่อนจะลงลึกเรื่อง Allocation ต้องเข้าใจก่อนว่า GPU Memory แบ่งออกเป็นส่วนหลักๆ ดังนี้:

สำหรับโมเดลเช่น Llama 3 70B Parameters ต้องใช้ Memory ประมาณ 140GB สำหรับ Weights เพียงอย่างเดียว (FP16) ซึ่งต้องใช้ GPU อย่างน้อย 2 ตัวสำหรับ Tensor Parallel และยังต้องเผื่อสำหรับ KV-Cache อีก

การตั้งค่า GPU Allocation ด้วย Python และ PyTorch

โค้ดต่อไปนี้แสดงการตั้งค่า GPU Allocation แบบ Dynamic ที่ปรับตาม Load จริง โดยใช้ HolySheep AI เป็น API Provider หลัก ซึ่งให้บริการด้วยราคาที่ประหยัดกว่า 85% เมื่อเทียบกับ OpenAI โดยมี Latency เฉลี่ยต่ำกว่า 50ms

import torch
import torch.nn as nn
from torch.cuda.amp import autocast, GradScaler
from typing import Dict, List, Optional, Tuple
import asyncio
from dataclasses import dataclass
from enum import Enum
import time

class LoadLevel(Enum):
    IDLE = 0
    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4

@dataclass
class GPUAllocationConfig:
    """Configuration สำหรับ Dynamic GPU Allocation"""
    base_batch_size: int = 8
    max_batch_size: int = 64
    min_batch_size: int = 1
    
    # Memory thresholds (as percentage)
    memory_low_threshold: float = 30.0
    memory_high_threshold: float = 75.0
    memory_critical_threshold: float = 90.0
    
    # Scaling factors
    scale_up_cooldown_seconds: float = 5.0
    scale_down_cooldown_seconds: float = 30.0
    scale_factor: float = 1.5
    
    # GPU settings
    preferred_gpu_memory_fraction: float = 0.9
    allow_growth: bool = True

class DynamicGPUAllocator:
    """
    Dynamic GPU Allocator สำหรับ AI Model Serving
    ปรับ Resource อัตโนมัติตาม Load จริง
    """
    
    def __init__(
        self,
        model: nn.Module,
        config: Optional[GPUAllocationConfig] = None
    ):
        self.model = model
        self.config = config or GPUAllocationConfig()
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        
        # State tracking
        self.current_batch_size = self.config.base_batch_size
        self.current_load_level = LoadLevel.IDLE
        self.last_scale_time = time.time()
        self.request_queue: asyncio.Queue = asyncio.Queue()
        self.is_processing = False
        
        # Performance metrics
        self.metrics = {
            'avg_latency': 0.0,
            'requests_per_second': 0.0,
            'gpu_utilization': 0.0,
            'memory_used_mb': 0.0,
        }
        
        # Initialize GPU memory management
        self._init_gpu_memory()
    
    def _init_gpu_memory(self):
        """ตั้งค่า GPU Memory เริ่มต้น"""
        if torch.cuda.is_available():
            # Clear cache ก่อน
            torch.cuda.empty_cache()
            torch.cuda.synchronize()
            
            # Set memory fraction
            if self.config.allow_growth:
                # Allow memory growth - เริ่มจากน้อย แล้วขยายตามจำเป็น
                torch.cuda.set_per_process_memory_fraction(
                    self.config.preferred_gpu_memory_fraction * 0.5  # เริ่มที่ 50%
                )
            else:
                torch.cuda.set_per_process_memory_fraction(
                    self.config.preferred_gpu_memory_fraction
                )
            
            # Get initial memory info
            allocated = torch.cuda.memory_allocated() / 1024**2
            reserved = torch.cuda.memory_reserved() / 1024**2
            print(f"GPU Memory Initialized: {allocated:.2f}MB allocated, {reserved:.2f}MB reserved")
    
    def get_memory_stats(self) -> Dict[str, float]:
        """ดึงข้อมูล GPU Memory ปัจจุบัน"""
        if not torch.cuda.is_available():
            return {'utilization': 0, 'used_mb': 0, 'total_mb': 0, 'percent': 0}
        
        allocated = torch.cuda.memory_allocated() / 1024**2
        reserved = torch.cuda.memory_reserved() / 1024**2
        total = torch.cuda.get_device_properties(0).total_memory / 1024**2
        
        return {
            'utilization': (allocated / total) * 100,
            'used_mb': allocated,
            'reserved_mb': reserved,
            'total_mb': total,
            'percent': (allocated / total) * 100
        }
    
    def determine_load_level(self) -> LoadLevel:
        """ตรวจสอบและกำหนด Load Level ปัจจุบัน"""
        mem_stats = self.get_memory_stats()
        queue_size = self.request_queue.qsize()
        
        # Calculate load based on multiple factors
        memory_percent = mem_stats['percent']
        queue_factor = min(queue_size / 100, 1.0) * 100  # Normalize to 0-100
        
        # Combined load score
        load_score = (memory_percent * 0.6) + (queue_factor * 0.4)
        
        if load_score < 20:
            return LoadLevel.IDLE
        elif load_score < 40:
            return LoadLevel.LOW
        elif load_score < 60:
            return LoadLevel.MEDIUM
        elif load_score < 80:
            return LoadLevel.HIGH
        else:
            return LoadLevel.CRITICAL
    
    def calculate_optimal_batch_size(self) -> int:
        """คำนวณ Batch Size ที่เหมาะสมตาม Load ปัจจุบัน"""
        load_level = self.determine_load_level()
        mem_stats = self.get_memory_stats()
        
        # Base multiplier ตาม Load Level
        multipliers = {
            LoadLevel.IDLE: 0.25,
            LoadLevel.LOW: 0.5,
            LoadLevel.MEDIUM: 1.0,
            LoadLevel.HIGH: 1.5,
            LoadLevel.CRITICAL: 2.0,
        }
        
        # Memory-based adjustment
        mem_adjustment = 1.0
        if mem_stats['percent'] > self.config.memory_high_threshold:
            mem_adjustment = 0.5  # ลด Batch Size ถ้า Memory สูง
        elif mem_stats['percent'] < self.config.memory_low_threshold:
            mem_adjustment = 1.5  # เพิ่ม Batch Size ถ้า Memory ต่ำ
        
        optimal_batch = int(
            self.config.base_batch_size * 
            multipliers[load_level] * 
            mem_adjustment
        )
        
        # Clamp to min/max
        return max(
            self.config.min_batch_size,
            min(optimal_batch, self.config.max_batch_size)
        )
    
    def adjust_allocation(self) -> Tuple[int, LoadLevel]:
        """
        ปรับ GPU Allocation ตามสถานการณ์ปัจจุบัน
        คืนค่า (new_batch_size, new_load_level)
        """
        current_time = time.time()
        
        # Check cooldown
        time_since_last_scale = current_time - self.last_scale_time
        
        new_load_level = self.determine_load_level()
        new_batch_size = self.calculate_optimal_batch_size()
        
        # Scale up immediately if critical
        if new_load_level == LoadLevel.CRITICAL:
            self.current_batch_size = new_batch_size
            self.last_scale_time = current_time
            print(f"⚡ URGENT: Scaled to batch size {new_batch_size} due to CRITICAL load")
        
        # Scale up if load increased
        elif new_load_level.value > self.current_load_level.value:
            if time_since_last_scale >= self.config.scale_up_cooldown_seconds:
                self.current_batch_size = new_batch_size
                self.last_scale_time = current_time
                print(f"📈 Scaled UP to batch size {new_batch_size} (Load: {new_load_level.name})")
        
        # Scale down only after longer cooldown
        elif new_load_level.value < self.current_load_level.value:
            if time_since_last_scale >= self.config.scale_down_cooldown_seconds:
                self.current_batch_size = new_batch_size
                self.last_scale_time = current_time
                print(f"📉 Scaled DOWN to batch size {new_batch_size} (Load: {new_load_level.name})")
        
        self.current_load_level = new_load_level
        return self.current_batch_size, new_load_level
    
    async def process_request(self, request_data: Dict) -> Dict:
        """ประมวลผล Request พร้อม Dynamic Batching"""
        await self.request_queue.put(request_data)
        
        # Try to batch process
        batch = []
        current_batch_size, _ = self.adjust_allocation()
        
        # Collect requests for batching
        timeout = 0.1  # 100ms max wait for batching
        start_time = time.time()
        
        while len(batch) < current_batch_size:
            elapsed = time.time() - start_time
            if elapsed > timeout:
                break
            
            try:
                remaining_timeout = timeout - elapsed
                request = await asyncio.wait_for(
                    self.request_queue.get(),
                    timeout=remaining_timeout
                )
                batch.append(request)
            except asyncio.TimeoutError:
                break
        
        if batch:
            # Process the batch
            result = await self._process_batch(batch)
            return result
        
        return {'status': 'queued', 'position': self.request_queue.qsize()}
    
    async def _process_batch(self, batch: List[Dict]) -> Dict:
        """ประมวลผล Batch ของ Requests"""
        self.is_processing = True
        start_time = time.time()
        
        try:
            # Move model to GPU if needed
            self.model.to(self.device)
            
            # Process batch with mixed precision
            with autocast():
                # Extract prompts
                prompts = [r.get('prompt', '') for r in batch]
                
                # TODO: Add actual model inference here
                # results = self.model.generate(prompts, batch_size=len(prompts))
                
                results = [{'generated_text': f'Response to: {p[:50]}...'} for p in prompts]
            
            # Update metrics
            latency = time.time() - start_time
            self.metrics['avg_latency'] = (
                self.metrics['avg_latency'] * 0.9 + latency * 0.1
            )
            self.metrics['requests_per_second'] = len(batch) / latency
            
            # Get final memory stats
            mem_stats = self.get_memory_stats()
            self.metrics['memory_used_mb'] = mem_stats['used_mb']
            self.metrics['gpu_utilization'] = mem_stats['utilization']
            
            # Clear cache periodically
            if torch.cuda.is_available():
                torch.cuda.empty_cache()
            
            return {
                'status': 'success',
                'results': results,
                'batch_size': len(batch),
                'latency_ms': latency * 1000,
                'memory_mb': mem_stats['used_mb']
            }
            
        except torch.cuda.OutOfMemoryError as e:
            print(f"🚨 CUDA OOM Error: {e}")
            # Scale down immediately
            self.current_batch_size = max(
                self.config.min_batch_size,
                int(self.current_batch_size * 0.5)
            )
            torch.cuda.empty_cache()
            
            return {
                'status': 'error',
                'error': 'GPU Memory Exhausted - Batch size reduced',
                'retry_after': 1.0
            }
        
        finally:
            self.is_processing = False
    
    def get_status(self) -> Dict:
        """ดึงสถานะระบบทั้งหมด"""
        return {
            'current_batch_size': self.current_batch_size,
            'load_level': self.current_load_level.name,
            'queue_size': self.request_queue.qsize(),
            'is_processing': self.is_processing,
            'metrics': self.metrics,
            'memory_stats': self.get_memory_stats()
        }


ตัวอย่างการใช้งาน

async def main(): """ตัวอย่างการใช้งาน DynamicGPUAllocator""" # Mock model (แทนที่ด้วยโมเดลจริง) class DummyModel(nn.Module): def __init__(self): super().__init__() self.embed = nn.Embedding(50000, 768) self.lm_head = nn.Linear(768, 50000) def forward(self, x): return x def generate(self, prompts, batch_size): return [{'text': f'Generated: {p}'} for p in prompts] model = DummyModel() config = GPUAllocationConfig( base_batch_size=16, max_batch_size=128, memory_high_threshold=80.0, memory_critical_threshold=95.0 ) allocator = DynamicGPUAllocator(model, config) print("🚀 Dynamic GPU Allocator Initialized") print(f"📊 Initial Status: {allocator.get_status()}") # Simulate varying load for i in range(5): # Simulate request request = { 'prompt': f'Test request number {i}', 'user_id': f'user_{i}', 'timestamp': time.time() } result = await allocator.process_request(request) print(f"✅ Request {i} processed: {result.get('status')}") # Show status after each request status = allocator.get_status() print(f"📈 Status: Batch={status['current_batch_size']}, Load={status['load_level']}, " f"Memory={status['memory_stats']['percent']:.1f}%") await asyncio.sleep(0.5) print("\n🎉 Demo completed!") if __name__ == "__main__": asyncio.run(main())

การใช้งาน HolySheep AI API สำหรับ Model Serving

สำหรับการ Deploy ระบบ AI ใน Production จริง การใช้ Managed API Service อย่าง HolySheep AI ช่วยลดภาระการจัดการ Infrastructure ได้มาก HolySheep ให้บริการด้วยราคาที่คุ้มค่ามาก เช่น DeepSeek V3.2 อยู่ที่ $0.42/MTok เทียบกับ GPT-4.1 ที่ $8/MTok ประหยัดได้ถึง 95% และรองรับการชำระเงินผ่าน WeChat Pay และ Alipay สำหรับลูกค้าในประเทศจีนอีกด้วย

โค้ดต่อไปนี้แสดงการตั้งค่า HolySheep API Client สำหรับ Enterprise RAG System:

import os
import httpx
import json
from typing import List, Dict, Optional, AsyncIterator
from dataclasses import dataclass, field
from datetime import datetime
import asyncio
from queue import Queue
import threading

============================================

HolySheep AI API Configuration

============================================

Base URL: https://api.holysheep.ai/v1

API Key: YOUR_HOLYSHEEP_API_KEY

#

HolySheep Pricing (2026):

- GPT-4.1: $8/MTok

- Claude Sonnet 4.5: $15/MTok

- Gemini 2.5 Flash: $2.50/MTok

- DeepSeek V3.2: $0.42/MTok

- DeepSeek R1: $0.42/MTok

#

Features: ¥1=$1 (85%+ savings), WeChat/Alipay support, <50ms latency

Register: https://www.holysheep.ai/register

============================================

@dataclass class HolySheepConfig: """Configuration สำหรับ HolySheep AI API""" base_url: str = "https://api.holysheep.ai/v1" api_key: str = "YOUR_HOLYSHEEP_API_KEY" # เปลี่ยนเป็น API Key จริง timeout: float = 60.0 max_retries: int = 3 retry_delay: float = 1.0 # Model settings default_model: str = "deepseek-v3.2" # ประหยัดที่สุด - $0.42/MTok default_temperature: float = 0.7 default_max_tokens: int = 2048 # Streaming settings stream_chunk_size: int = 16 # Rate limiting requests_per_minute: int = 60 tokens_per_minute: int = 100000 class RateLimiter: """Rate Limiter สำหรับ API Calls""" def __init__(self, rpm: int, tpm: int): self.rpm = rpm self.tpm = tpm self.request_timestamps: List[float] = [] self.token_counts: List[tuple] = [] # (timestamp, token_count) self._lock = threading.Lock() def acquire(self, estimated_tokens: int = 0) -> float: """ขออนุญาตส่ง Request คืนค่า wait_time (วินาที)""" with self._lock: now = datetime.now().timestamp() # Clean old timestamps self.request_timestamps = [ ts for ts in self.request_timestamps if now - ts < 60 ] self.token_counts = [ (ts, count) for ts, count in self.token_counts if now - ts < 60 ] # Check RPM limit if len(self.request_timestamps) >= self.rpm: oldest = min(self.request_timestamps) wait_rpm = 60 - (now - oldest) else: wait_rpm = 0 # Check TPM limit total_tokens_last_minute = sum( count for ts, count in self.token_counts ) if total_tokens_last_minute + estimated_tokens > self.tpm: if self.token_counts: oldest = min(ts for ts, _ in self.token_counts) wait_tpm = 60 - (now - oldest) else: wait_tpm = 60 else: wait_tpm = 0 wait_time = max(wait_rpm, wait_tpm) return wait_time def record(self, token_count: int): """บันทึกการใช้งาน""" with self._lock: now = datetime.now().timestamp() self.request_timestamps.append(now) self.token_counts.append((now, token_count)) class HolySheepAIClient: """ HolySheep AI API Client สำหรับ Enterprise RAG System Features: - Synchronous และ Asynchronous API calls - Automatic retry with exponential backoff - Streaming response support - Token usage tracking - Rate limiting - Connection pooling """ def __init__(self, config: Optional[HolySheepConfig] = None): self.config = config or HolySheepConfig() self.rate_limiter = RateLimiter( self.config.requests_per_minute, self.config.tokens_per_minute ) # Setup HTTP client with connection pooling self._client = httpx.Client( base_url=self.config.base_url, timeout=self.config.timeout, limits=httpx.Limits(max_keepalive_connections=20, max_connections=100) ) # Setup async HTTP client self._async_client = None # Usage tracking self.total_tokens_used = 0 self.total_requests = 0 self.total_cost = 0.0 # Model pricing (USD per 1M tokens) self.model_pricing = { "gpt-4.1": 8.0, "claude-sonnet-4.5": 15.0, "gemini-2.5-flash": 2.50, "deepseek-v3.2": 0.42, "deepseek-r1": 0.42, # เพิ่มโมเดลอื่นๆ ตามความจำเป็น } def _get_headers(self) -> Dict[str, str]: """สร้าง Headers สำหรับ API Request""" return { "Authorization": f"Bearer {self.config.api_key}", "Content-Type": "application/json", "Accept": "application/json" } def _estimate_tokens(self, text: str) -> int: """ประมาณการใช้ Token (Rough estimation)""" # โดยเฉลี่ย 1 token ≈ 4 characters สำหรับภาษาอังกฤษ # สำหรับภาษาไทย ประมาณ 1 token ≈ 2 characters return len(text) // 2 def _calculate_cost(self, model: str, prompt_tokens: int, completion_tokens: int) -> float: """คำนวณค่าใช้จ่าย (สมมติว่า prompt และ completion ใช้ราคาเท่ากัน)""" price = self.model_pricing.get(model, 0.42) # Default to DeepSeek price total_tokens = prompt_tokens + completion_tokens return (total_tokens / 1_000_000) * price def chat_completions( self, messages: List[Dict[str, str]], model: Optional[str] = None, temperature: Optional[float] = None, max_tokens: Optional[int] = None, stream: bool = False, **kwargs ) -> Dict: """ ส่ง Chat Completion Request ไปยัง HolySheep API Args: messages: รายการ message objects [{role, content}, ...] model: ชื่อโมเดล (default: deepseek-v3.2) temperature: ค่า temperature (0-2) max_tokens: จำนวน token สูงสุดของ response stream: เปิด streaming หรือไม่ Returns: Response dict ที่มี choices, usage, และข้อมูลอื่นๆ """ model = model or self.config.default_model temperature = temperature or self.config.default_temperature max_tokens = max_tokens or self.config.default_max_tokens # Estimate tokens for rate limiting estimated_input_tokens = sum( self._estimate_tokens(m.get('content', '')) for m in messages ) estimated_total_tokens = estimated_input_tokens + max_tokens # Wait for rate limit wait_time = self.rate_limiter.acquire(estimated_total_tokens) if wait_time > 0: import time print(f"⏳ Rate limit reached, waiting {wait_time:.2f}s") time.sleep(wait_time) # Build request payload payload = { "model": model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, "stream": stream, **kwargs } # Remove None values payload = {k: v for k, v in payload.items() if v is not None} # Make request with retry url = f"{self.config.base_url}/chat/completions" headers = self._get_headers() for attempt in range(self.config.max_retries): try: response = self._client.post( url, json=payload, headers=headers ) response.raise_for_status() result = response.json() # Update usage tracking usage = result.get('usage', {}) prompt_tokens = usage.get('prompt_tokens', 0) completion_tokens = usage.get('completion_tokens', 0) total_tokens = usage.get