SiamCafe.net Blog
Technology

Text Generation WebUI SaaS Architecture

text generation webui saas architecture
Text Generation WebUI SaaS Architecture | SiamCafe Blog
2026-01-07· อ. บอม — SiamCafe.net· 11,697 คำ

Text Generation WebUI คืออะไร

Text Generation WebUI เป็น Open-source Web Interface สำหรับรัน Large Language Model (LLM) แบบ Self-hosted พัฒนาโดย oobabooga รองรับ Model หลากหลายเช่น Llama 3, Mistral, Phi-3, Qwen และ Gemma ผ่าน Backend หลายตัวเช่น llama.cpp, ExLlamaV2, Transformers และ vLLM ผู้ใช้สามารถ Chat กับ AI, ปรับ Parameters, สลับ Model และใช้ Extensions ต่างๆผ่าน Browser

เมื่อต้องการให้บริการ Text Generation ให้ผู้ใช้หลายคน เช่น ภายในองค์กรหรือเป็น SaaS Product ต้องออกแบบ Architecture ที่รองรับ Multi-tenant, GPU Resource Management, Authentication, Rate Limiting และ Billing ซึ่งมีความซับซ้อนมากกว่าการรันบนเครื่องส่วนตัว

SaaS Architecture Overview

การตั้งค่า vLLM เป็น Inference Backend

# docker-compose.yml สำหรับ Text Generation SaaS
version: "3.8"

services:
  # API Gateway
  kong:
    image: kong:3.5
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /kong/kong.yml
      KONG_PROXY_LISTEN: "0.0.0.0:8000, 0.0.0.0:8443 ssl"
    volumes:
      - ./kong.yml:/kong/kong.yml
    ports:
      - "8000:8000"
      - "8443:8443"

  # Redis สำหรับ Queue และ Rate Limiting
  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 2gb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data

  # vLLM Inference Server (GPU Node 1)
  vllm-worker-1:
    image: vllm/vllm-openai:latest
    command: >
      --model meta-llama/Llama-3.1-8B-Instruct
      --max-model-len 8192
      --gpu-memory-utilization 0.90
      --max-num-seqs 32
      --tensor-parallel-size 1
      --port 8080
      --api-key 
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    environment:
      HUGGING_FACE_HUB_TOKEN: 

  # vLLM Inference Server (GPU Node 2 — Model อื่น)
  vllm-worker-2:
    image: vllm/vllm-openai:latest
    command: >
      --model mistralai/Mistral-7B-Instruct-v0.3
      --max-model-len 8192
      --gpu-memory-utilization 0.90
      --max-num-seqs 32
      --port 8080
      --api-key 
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    environment:
      HUGGING_FACE_HUB_TOKEN: 

  # Auth + API Service
  api-server:
    build: ./api-server
    environment:
      REDIS_URL: redis://redis:6379
      DATABASE_URL: postgresql://app:@postgres:5432/saas
      JWT_SECRET: 
      VLLM_ENDPOINTS: "vllm-worker-1:8080, vllm-worker-2:8080"
    depends_on:
      - redis
      - postgres

  # PostgreSQL สำหรับ User/Billing Data
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: saas
      POSTGRES_USER: app
      POSTGRES_PASSWORD: 
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  redis_data:
  postgres_data:

---
# kong.yml — API Gateway Configuration
_format_version: "3.0"

services:
  - name: llm-api
    url: http://api-server:3000
    routes:
      - name: llm-route
        paths:
          - /v1/chat/completions
          - /v1/completions
          - /v1/models
        strip_path: false

plugins:
  - name: rate-limiting
    config:
      minute: 60
      policy: redis
      redis_host: redis
      redis_port: 6379

  - name: key-auth
    config:
      key_names:
        - Authorization
        - X-API-Key

  - name: cors
    config:
      origins:
        - "*"
      methods:
        - GET
        - POST
        - OPTIONS
      headers:
        - Authorization
        - Content-Type

API Server กับ Multi-tenant Logic

# api-server/main.py — FastAPI Server สำหรับ Multi-tenant LLM
from fastapi import FastAPI, HTTPException, Depends, Header
from pydantic import BaseModel
import httpx
import asyncio
import redis.asyncio as redis
import json
import time
from datetime import datetime

app = FastAPI(title="LLM SaaS API")

# Redis Connection
redis_client = redis.from_url("redis://redis:6379", decode_responses=True)

# vLLM Endpoints
VLLM_ENDPOINTS = {
    "llama-3.1-8b": "http://vllm-worker-1:8080",
    "mistral-7b": "http://vllm-worker-2:8080",
}

class ChatRequest(BaseModel):
    model: str = "llama-3.1-8b"
    messages: list[dict]
    max_tokens: int = 1024
    temperature: float = 0.7
    stream: bool = False

class TenantInfo:
    def __init__(self, tenant_id, plan, rate_limit, token_limit):
        self.tenant_id = tenant_id
        self.plan = plan
        self.rate_limit = rate_limit
        self.token_limit = token_limit

async def authenticate(authorization: str = Header(...)) -> TenantInfo:
    """Authenticate API Key และดึง Tenant Info"""
    api_key = authorization.replace("Bearer ", "")
    tenant_data = await redis_client.hgetall(f"apikey:{api_key}")
    if not tenant_data:
        raise HTTPException(status_code=401, detail="Invalid API key")
    return TenantInfo(
        tenant_id=tenant_data["tenant_id"],
        plan=tenant_data["plan"],
        rate_limit=int(tenant_data.get("rate_limit", 60)),
        token_limit=int(tenant_data.get("token_limit", 100000)),
    )

async def check_rate_limit(tenant: TenantInfo):
    """ตรวจสอบ Rate Limit ต่อนาที"""
    key = f"ratelimit:{tenant.tenant_id}:{int(time.time()) // 60}"
    count = await redis_client.incr(key)
    await redis_client.expire(key, 120)
    if count > tenant.rate_limit:
        raise HTTPException(status_code=429, detail="Rate limit exceeded")

async def track_usage(tenant_id: str, model: str, prompt_tokens: int, completion_tokens: int):
    """บันทึก Token Usage สำหรับ Billing"""
    today = datetime.now().strftime("%Y-%m-%d")
    usage_key = f"usage:{tenant_id}:{today}"
    await redis_client.hincrby(usage_key, "prompt_tokens", prompt_tokens)
    await redis_client.hincrby(usage_key, "completion_tokens", completion_tokens)
    await redis_client.hincrby(usage_key, "requests", 1)
    await redis_client.expire(usage_key, 86400 * 90)  # เก็บ 90 วัน

@app.post("/v1/chat/completions")
async def chat_completions(
    request: ChatRequest,
    tenant: TenantInfo = Depends(authenticate),
):
    await check_rate_limit(tenant)

    # เลือก vLLM Endpoint ตาม Model
    endpoint = VLLM_ENDPOINTS.get(request.model)
    if not endpoint:
        raise HTTPException(status_code=400, detail=f"Model {request.model} not available")

    # Forward Request ไป vLLM
    async with httpx.AsyncClient(timeout=120.0) as client:
        resp = await client.post(
            f"{endpoint}/v1/chat/completions",
            json=request.model_dump(),
            headers={"Authorization": f"Bearer {VLLM_API_KEY}"},
        )

    result = resp.json()

    # Track Usage
    usage = result.get("usage", {})
    await track_usage(
        tenant.tenant_id,
        request.model,
        usage.get("prompt_tokens", 0),
        usage.get("completion_tokens", 0),
    )

    return result

@app.get("/v1/models")
async def list_models(tenant: TenantInfo = Depends(authenticate)):
    """List Available Models ตาม Tenant Plan"""
    models = list(VLLM_ENDPOINTS.keys())
    if tenant.plan == "free":
        models = [m for m in models if "8b" in m.lower()]
    return {"data": [{"id": m, "object": "model"} for m in models]}

GPU Scheduling และ Autoscaling

# kubernetes/gpu-autoscaler.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-llama
  namespace: llm-saas
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vllm-llama
  template:
    metadata:
      labels:
        app: vllm-llama
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:latest
          args:
            - "--model"
            - "meta-llama/Llama-3.1-8B-Instruct"
            - "--max-model-len"
            - "8192"
            - "--gpu-memory-utilization"
            - "0.90"
            - "--max-num-seqs"
            - "32"
          resources:
            limits:
              nvidia.com/gpu: 1
              memory: 32Gi
            requests:
              nvidia.com/gpu: 1
              memory: 24Gi
          ports:
            - containerPort: 8000
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 120
            periodSeconds: 10
      nodeSelector:
        gpu-type: a100
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: vllm-llama-hpa
  namespace: llm-saas
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: vllm-llama
  minReplicas: 1
  maxReplicas: 8
  metrics:
    - type: Pods
      pods:
        metric:
          name: vllm_num_requests_running
        target:
          type: AverageValue
          averageValue: "20"
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 2
          periodSeconds: 120
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 1
          periodSeconds: 300

Monitoring และ Cost Management

Text Generation WebUI คืออะไร

Text Generation WebUI เป็น Open-source Web Interface สำหรับรัน LLM แบบ Local รองรับ Model เช่น Llama, Mistral, Phi ผ่าน Backend หลายตัว ให้ผู้ใช้ Chat กับ AI ผ่าน Browser โดยไม่ต้องส่งข้อมูลไป Cloud เหมาะสำหรับองค์กรที่ต้องการ Data Privacy

ทำไมต้องสร้างเป็น SaaS Architecture

เพื่อให้บริการผู้ใช้หลายคนพร้อมกัน จัดการ GPU Resources อย่างมีประสิทธิภาพไม่ปล่อยให้ GPU ว่าง คิดค่าบริการตาม Token Usage มี Authentication, Rate Limiting และ Tenant Isolation ป้องกันผู้ใช้รายหนึ่งกระทบอีกราย

Multi-tenant Architecture สำหรับ LLM ต้องออกแบบอย่างไร

ต้องมี API Gateway จัดการ Auth และ Rate Limit, Queue System จัดคิว Request ตาม Priority, GPU Scheduler จัดสรร GPU, vLLM/TGI เป็น Inference Backend, Usage Tracker บันทึก Token Usage และ Billing Service คำนวณค่าบริการ ทั้งหมดรันบน Kubernetes สำหรับ Autoscaling

ต้องใช้ GPU อะไรสำหรับ Self-hosted LLM

7B Parameter Model ใช้ RTX 4090 (24GB VRAM), 13B ใช้ A100 40GB, 70B ต้องใช้ A100 80GB หรือหลายใบ Quantized Model (GGUF Q4) ใช้ VRAM น้อยกว่าประมาณ 50-60% สำหรับ Production แนะนำ A100 หรือ H100 เพราะมี Throughput สูงกว่า Consumer GPU

สรุปและแนวทางปฏิบัติ

การสร้าง Text Generation SaaS ต้องออกแบบ Architecture ที่รองรับ Multi-tenant อย่างมีประสิทธิภาพ ใช้ vLLM หรือ TGI เป็น Inference Backend สำหรับ Throughput สูง, Kong หรือ Nginx เป็น API Gateway, Redis สำหรับ Queue และ Rate Limiting และ Kubernetes + HPA สำหรับ Autoscaling ตาม GPU Load สิ่งสำคัญคือต้อง Monitor GPU Utilization อย่างใกล้ชิด คำนวณ Cost per Token ให้แม่นยำ และมี Tenant Isolation ที่ดีเพื่อป้องกัน Noisy Neighbor Problem

📖 บทความที่เกี่ยวข้อง

Text Generation WebUI API Integration เชื่อมต่อระบบอ่านบทความ → Text Generation WebUI Incident Managementอ่านบทความ → Text Generation WebUI สำหรับมือใหม่ Step by Stepอ่านบทความ → Text Generation WebUI Code Review Best Practiceอ่านบทความ → Text Generation WebUI CI CD Automation Pipelineอ่านบทความ →

📚 ดูบทความทั้งหมด →