ผมเคยใช้เวลาหลายสัปดาห์ในการดีพลอย Llama 3 70B บนคลัสเตอร์ GPU ของตัวเอง ก่อนจะรู้ว่า "รันโมเดลได้" กับ "ให้บริการในระดับ Production" มันคนละเรื่องกันเลย บทความนี้คือบันทึกสนามจริงเกี่ยวกับ TGI (Text Generation Inference) ของ HuggingFace — ตั้งแต่สถาปัตยกรรมภายใน, เทคนิคเพิ่มประสิทธิภาพ, การควบคุม Concurrency ไปจนถึงการคำนวณต้นทุนเทียบกับการใช้ API ภายนอกอย่าง HolySheep AI

1. TGI คืออะไร และทำไมต้องเลือก TGI

TGI เป็น inference server ที่ HuggingFace พัฒนาขึ้นเพื่อให้บริการ Large Language Models ในระดับ production โดยเฉพาะ จุดเด่นคือ:

2. สถาปัตยกรรมภายในของ TGI

TGI ใช้สถาปัตยกรรมสองชั้น:

จุดที่ทำให้ TGI เร็วกว่า vanilla HuggingFace transformers คือ prefix sharing และ copy-on-write scheduling — request ที่มี system prompt เหมือนกันสามารถแชร์ KV cache ส่วน prefix ได้ ลด computation ลงไปได้มหาศาลในงานที่มี system prompt ยาว เช่น RAG

3. การติดตั้งและ Deploy

วิธีที่ผมแนะนำคือใช้ official Docker image เพราะ TGI build ค่อนข้างซับซ้อน (ต้องการ CUDA toolkit, Rust nightly, flash-attn wheel):

# Deploy Llama 3 70B ด้วย AWQ INT4 บน 2x A100 80GB
docker run -d \
  --name tgi-llama3-70b \
  --gpus '"device=0,1"' \
  --shm-size 32g \
  -p 8080:80 \
  -v $HOME/models:/data \
  -e HF_TOKEN=$HF_TOKEN \
  ghcr.io/huggingface/text-generation-inference:2.0.4 \
  --model-id meta-llama/Meta-Llama-3-70B-Instruct \
  --quantize awq \
  --num-shard 2 \
  --max-concurrent-requests 256 \
  --max-batch-prefill-tokens 16384 \
  --max-batch-total-tokens 65536 \
  --max-input-length 8192 \
  --max-total-tokens 10240 \
  --enable-prefix-caching \
  --hostname 0.0.0.0 \
  --port 80

ตรวจสอบสถานะ

curl -s http://localhost:8080/health | jq

{"status":"ok","model_id":"meta-llama/Meta-Llama-3-70B-Instruct"}

curl -s http://localhost:8080/metrics | grep -E "tgi_"

คำอธิบาย flag ที่สำคัญ:

4. Production Client Code (Python)

นี่คือ client ที่ผมใช้งานจริงใน production รองรับทั้ง streaming, retry และ connection pooling:

import asyncio
import time
from typing import AsyncIterator
from openai import AsyncOpenAI

==== TGI (Self-hosted) ====

tgi_client = AsyncOpenAI( base_url="http://localhost:8080/v1", api_key="not-needed", timeout=60.0, max_retries=3, ) async def stream_chat_tgi(messages, model="meta-llama/Meta-Llama-3-70B-Instruct"): start = time.perf_counter() first_token_time = None token_count = 0 stream = await tgi_client.chat.completions.create( model=model, messages=messages, stream=True, temperature=0.7, top_p=0.9, max_tokens=1024, ) async for chunk in stream: delta = chunk.choices[0].delta.content if delta: if first_token_time is None: first_token_time = time.perf_counter() token_count += 1 yield delta end = time.perf_counter() ttft_ms = (first_token_time - start) * 1000 if first_token_time else 0 total_ms = (end - start) * 1000 tps = token_count / (end - first_token_time) if first_token_time else 0 print(f"[TGI] TTFT={ttft_ms:.0f}ms total={total_ms:.0f}ms TPS={tps:.1f}")

5. Performance Tuning — ตัวเลขจริงจากการ Benchmark

ผมทดสอบบน 2x A100 80GB (NVLink) โหลด real-world RAG workload (input 2K tokens, output 500 tokens) ผลลัพธ์:

Insight: Quantization แบบ AWQ INT4 ให้ throughput เพิ่มขึ้นประมาณ 40% และ memory ลดลง ~3.7x โดย quality loss น้อยมาก (MMLU ลด < 0.5%) ส่วน continuous batching ช่วยเพิ่ม aggregate throughput ได้ถึง 8-12 เท่าเมื่อเทียบกับ single-request serving

6. การควบคุม Concurrency และ Backpressure

ปัญหาใหญ่ของ self-hosted LLM คือเมื่อโหลดเกิน capacity จะเกิด OOM ทันที TGI แก้ปัญหานี้ด้วย 2 ชั้น:

# 1) TGI เองจะคืน HTTP 503 เมื่อ queue เต็ม

Client ต้อง handle 503 ด้วย exponential backoff

2) ใช้ reverse proxy (nginx) เป็น buffer อีกชั้น

/etc/nginx/nginx.conf

upstream tgi_backend { server localhost:8080; keepalive 32; } server { listen 80; location /v1/ { proxy_pass http://tgi_backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_buffering off; # สำคัญ: ต้องปิดสำหรับ SSE streaming proxy_read_timeout 300s; proxy_send_timeout 300s; # Rate limit per IP limit_req zone=llm_api burst=20 nodelay; limit_req_status 429; } }

หรือใช้ API Gateway อย่าง Kong / Envoy

เพื่อทำ circuit breaker + rate limit + quota

สูตรคำนวณ capacity: max_concurrent = (GPU_memory - model_size) / (2 * KV_cache_per_token * max_seq_len) เช่น Llama 3 70B AWQ (~35GB) บน A100 80GB หนึ่งใบ, ที่ seq_len 8192, KV cache ~0.5MB/token จะรับได้ประมาณ (80-35) / (2 * 0.5 * 8192 / 1024) ≈ 5.6 concurrent requests ต่อ GPU

7. การคำนวณต้นทุน — Self-host vs API

มาคำนวณกันตรงๆ สมมติใช้งาน 50 ล้าน tokens/เดือน (input + output):

เห็นชัดเลยว่า self-host คุ้มเฉพาะเมื่อใช้งานหนักมากๆ (>500M tokens/เดือน) หรือมีข้อจำกัดเรื่อง data sovereignty จริงๆ สำหรับงานทั่วไป การใช้ API ที่มี aggregated traffic จะประหยัดกว่ามาก

และถ้าเทียบในมิติ latency: HolySheep รายงาน TTFT < 50ms ซึ่งเร็วกว่า self-hosted TGI ที่ผมวัดได้ (62-320ms) เสียอีก เพราะมี edge infrastructure กระจายทั่วโลก รับชำระผ่าน WeChat/Alipay ได้ และอัตราแลกเปลี่ยน ¥1 = $1 ทำให้ประหยัดเพิ่มอีก 85%+ เมื่อเทียบกับ provider ตะวันตก และยังมีเครดิตฟรีให้ทดลองเมื่อลงทะเบียน

# ตัวอย่าง production code ที่ใช้ HolySheep เป็น primary

และ fallback ไป TGI self-hosted เมื่อ HolySheep down

import os from openai import AsyncOpenAI HOLYSHEEP_KEY = os.environ["HOLYSHEEP_API_KEY"]

==== HolySheep AI (Primary) ====

holysheep = AsyncOpenAI( base_url="https://api.holysheep.ai/v1", api_key=HOLYSHEEP_KEY, timeout=30.0, max_retries=2, )

==== Self-hosted TGI (Fallback) ====

tgi_fallback = AsyncOpenAI( base_url="http://internal-lm.internal:8080/v1", api_key="not-needed", timeout=60.0, ) async def robust_chat(messages, model="deepseek-chat"): """Try HolySheep first, fallback to self-hosted TGI.""" for client, label, m in [ (holysheep, "HolySheep", model), (tgi_fallback, "TGI-fallback", "meta-llama/Meta-Llama-3-70B-Instruct"), ]: try: resp = await client.chat.completions.create( model=m, messages=messages, stream=False, max_tokens=1024 ) return {"provider": label, "content": resp.choices[0].message.content} except Exception as e: print(f"[{label}] failed: {e!r}, switching...") raise RuntimeError("All providers down")

วิธีใช้

result = await robust_chat([{"role": "user", "content": "สวัสดี"}])

print(result)

8. Production Checklist

ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

ข้อผิดพลาดที่ 1: CUDA Out of Memory เมื่อโหลดสูง

อาการ: RuntimeError: CUDA out of memory. Tried to allocate 256.00 MiB ใน log ของ TGI container

สาเหตุ: ตั้ง --max-concurrent-requests สูงเกินไป KV cache ล้น VRAM

# ❌ ผิด: ตั้ง concurrent สูงโดยไม่คำนวณ
--max-concurrent-requests 512

✅ ถูก: คำนวณจาก VRAM

สูตร: (80GB - 35GB model) / (2 * 0.5MB * 8192 / 1024) ≈ 5 concurrent

ตั้ง buffer ไว้ที่ 80% ของ capacity

--max-concurrent-requests 4 --max-batch-total-tokens 32768 # ลดลงด้วยถ้า seq ยาว --max-batch-prefill-tokens 8192

ถ้ายัง OOM: ลด max-input-length

--max-input-length 4096 --max-total-tokens 5120

หรือเปลี่ยน quantization เป็น AWQ INT4 (ลด memory ~3.7x)

--quantize awq

ข้อผิดพลาดที่ 2: Streaming ค้าง / Connection Reset ระหวัส 30 วินาที

อาการ: เรียก stream=True ได้ token แรก แล้วเงียบ หรือ ProxyError: Connection reset by peer

สาเหตุ: Nginx buffer streaming response, timeout เริ่มต้นสั้นเกินไป

# ❌ ผิด: ใช้ nginx default config
location /v1/ {
    proxy_pass http://tgi_backend;
}

✅ ถูก: ปิด buffering และเพิ่ม timeout

location /v1/ { proxy_pass http://tgi_backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_buffering off; # จำเป็นสำหรับ SSE proxy_cache off; proxy_read_timeout 600s; # สำหรับ long generation proxy_send_timeout 600s; proxy_connect_timeout 10s; chunked_transfer_encoding on; # สำหรับ streaming # ปิด compression ที่ทำให้ streaming พัง gzip off; }

ถ้าใช้ ALB/Cloudflare หน้า Nginx: เพิ่ม idle timeout ที่นั่นด้วย

AWS ALB default idle timeout = 60s

ข้อผิดพลาดที่ 3: 503 Service Unavailable ทั้งที่ GPU ยังว่าง

อาการ: TGI คืน 503 แม้ utilization ต่ำ ดูใน /metrics เห็น tgi_queue_size ไม่ใหญ่

สาเหตุ: request ใหม่ถูก block เพราะ prefill batch เต็ม หรือมี request เก่าค้างใน batch

# ✅ วิธีแก้: แยก prefill/decode budget ให้ดีขึ้น

ปรับ flag เหล่านี้:

--max-batch-prefill-tokens 8192 # ไม่ใช่ 16384 (เพราะ block decode) --max-batch-total-tokens 32768 --max-waiting-tokens 20 # จำกัด queue เพื่อป้องกัน starvation

✅ เพิ่ม client retry logic

import backoff from openai import APITimeoutError, APIError @backoff.on_exception( backoff.expo, (APITimeoutError, APIError), max_tries=4, max_time=30, ) async def chat_with_retry(client, **kwargs): return await client.chat.completions.create(**kwargs)

✅ ใช้ connection pool แทนการสร้าง client ใหม่ทุกครั้ง

import httpx limits = httpx.Limits( max_connections=100, max_keepalive_connections=20, keepalive_expiry=30, ) client = AsyncOpenAI( base_url="https://api.holysheep.ai/v1", api_key="YOUR_HOLYSHEEP_API_KEY", http_client=httpx.AsyncClient(limits=limits, timeout=30.0), )

สรุป

TGI เป็นตัวเลือกที่ดีที่สุดตัวหนึ่งสำหรับ self-hosted LLM ในระดับ production — มี continuous batching, paged attention, และ OpenAI-compatible API ครบ ถ้าทีมมี capacity ที่จะ monitor, scale, และ maintain GPU cluster การ self-host จะคุ้มค่าเมื่อใช้งานหนักมากๆ หรือต้องการ data sovereignty

แต่ถ้าทีมยังต้องการความยืดหยุ่นและ latency ต่ำกว่า 50ms โดยไม่ต้องลงทุน GPU หลักแสน แนะนำให้เริ่มจาก API gateway อย่าง HolySheep AI ก่อน — รองรับ GPT-4.1, Claude Sonnet 4.5, Gemini 2.5 Flash, DeepSeek V3.2 ที่ราคา $0.42-$15/MTok, จ่ายผ่าน WeChat/Alipay ได้, อัตรา ¥1=$1 ประหยัด 85%+ และได้เครดิตฟรีเมื่อลงทะเบียน

👉 สมัคร HolySheep AI — รับเครดิตฟรีเมื่อลงทะเบียน