Thuta Learning
Local AI / Local LLM
AdvancedAIbeginner

Serving, Docker နှင့် Hybrid Architecture

ဒီခန်းပြီးရင် ဘာတတ်သွားမလဲ

  • Serving, Docker နှင့် Hybrid Architecture concept ကို နားလည်ရှင်းပြနိုင်ရန်
  • Diagram ကို ဖတ်ပြီး architecture ထဲမှာ data/request ဘယ်လိုစီးဆင်းသလဲ ခြေရာခံနိုင်ရန်
  • ကိုယ့် hardware/use case အတွက် ဘယ်လို ရွေးချယ်သင့်သလဲ ဆုံးဖြတ်နိုင်ရန်

နားလည်ထားရမယ့် အချက်

Interactive user တစ်ယောက်အတွက် တည်ဆောက်ထားတဲ့ model server တစ်ခုက user အများအတွက် automatic well ကျင့်တာ မဟုတ်ပါဘူး။ Concurrency ကတော့ core issue ပါ — generation တစ်ခုချင်းစီပဲ run နေတဲ့ GPU တစ်ခုက stateless web API တစ်ခုလို request ငါးခုကို parallel run လို့ မရပါဘူး၊ request တစ်ခုစီက generation ကြာချိန်တလျှောက် GPU memory (weight plus ၎င်းရဲ့ KV cache) ကို ကိုင်ထားလို့ပါ။

Real serving setup တွေက ဒါကို mechanism သုံးခုနဲ့ ကိုင်တွယ်ပါတယ် —

  • Request queuing — တစ်ပြိုင်နက် run နိုင်တာထက်ပိုတဲ့ request တွေကို လက်ခံပြီး အစဉ်လိုက် ဒါမှမဟုတ် priority အရ process လုပ်ခြင်း
  • Batching — engine တချို့က prompt အများစုရဲ့ decode step တွေကို အတူတကွ process လုပ်နိုင်ပါတယ်၊ request တစ်ခုချင်းစီရဲ့ latency အနည်းငယ် ကုန်ကျပြီး throughput တိုးတက်စေတယ်
  • Rate limiting — client တစ်ယောက်ချင်းစီ ရနိုင်မယ့် concurrent ဒါမှမဟုတ် per-minute request အရေအတွက် ကန့်သတ်၊ heavy user တစ်ယောက်က တခြားလူတွေကို starve မဖြစ်စေအောင်

Docker နဲ့ local model တစ်ခုကို containerize လုပ်တာက performance ထက် isolation နဲ့ reproducibility အတွက် အများစုပါ။ Typical shape ကတော့ container နှစ်ခု ပါဝင်ပါတယ် — GPU passthrough ပါတဲ့ inference server တစ်ခု run နေတဲ့ container တစ်ခု၊ ၎င်းကို ခေါ်တဲ့ application တစ်ခု run နေတဲ့ container တစ်ခု၊ host ရဲ့ public interface မဟုတ်ဘဲ private Docker network ကတဆင့် ချိတ်ဆက်ထားပါတယ်။

Model weight တွေအတွက် named volume တစ်ခု ရှိပြီး၊ multi-gigabyte download တစ်ခုက container rebuild တိုင်း ပြန် download ဆွဲစရာမလိုဘဲ ရှင်သန်စေပါတယ်။

Inference Server ရဲ့ Port ကို Publish မလုပ်ပါနှင့်

Inference server ရဲ့ port ကို default အနေနဲ့ host ဆီ publish မလုပ်သင့်ပါဘူး — application container တစ်ခုတည်းသာ internal network ကတဆင့် ၎င်းဆီ ရောက်ဖို့ လိုပါတယ်။

Local network server တစ်ခုက ဒါကို device များစွာဆီ ချဲ့ထွင်ပေးပါတယ် — capable GPU ပါတဲ့ စက်တစ်လုံးက LAN တူတူပေါ်က laptop, phone တွေဆီ model serve ပေးပါတယ်၊ exposed service တခြားတွေအတွက် authentication နဲ့ TLS consideration အတူတူ လိုအပ်ပါတယ်၊ 'home network ချည်း' ဆိုပြီး exception မရှိပါဘူး။

Hybrid routing က request တိုင်းအတွက် model တစ်ခုတည်း မှန်ကန်တာ မဟုတ်ဘူးဆိုတာကို အသိအမှတ်ပြုပါတယ် —

  • Sensitive ဒါမှမဟုတ် simple query တွေကို local model သေးသေးလေးဆီ route လုပ်ပါ
  • Hardware ခွင့်ပြုရင် ပိုစွမ်းရည်လိုအပ်တဲ့ request တွေကို local model ကြီးကြီးဆီ route လုပ်ပါ
  • Local hardware serve နိုင်တာထက်ပိုတဲ့ capability လိုအပ်ပြီး data sensitive မဟုတ်တဲ့ request ဘယ်ဟာမဆိုကို cloud model ဆီ route လုပ်ပါ

Task property ကိုကြည့်ပြီး ဆုံးဖြတ်ပါ၊ fixed default တစ်ခုတည်းနဲ့ မဟုတ်ပါဘူး။

text
HYBRID ROUTER: CHOOSING A MODEL PER REQUEST
-------------------------------------------
HYBRID ROUTER: CHOOSING A MODEL PER REQUEST
------------------------------------------------

                     incoming request
                            |
                            v
                    +---------------+
                    |    router     |
                    +---------------+
                     /      |       \
                    /       |        \
           sensitive/    needs more   needs capability
           simple task   local power  beyond local HW,
                |             |        data not sensitive
                v             v              v
        small local     larger local     cloud model
          model            model         (external API)
        (fast, cheap)  (slower, better) (most capable,
                                          leaves the machine)

လက်တွေ့ scenario နဲ့ ချိတ်ကြည့်မယ်

အပေါ်က shape ကတော့ exposure အပေါ်မှာ တမင်တကာ conservative ဖြစ်ပါတယ် — `model-server` မှာ host ဆီ `-p`/`ports:` mapping လုံးဝ မရှိပါဘူး၊ ဒါကြောင့် ၎င်းဆီ ရောက်နိုင်တဲ့ တစ်ခုတည်းသောနည်းလမ်းက Docker network တူတူပေါ်က container တခြားက ဖြစ်ပါတယ် — app container အတိအကျ ၎င်းရဲ့ internal hostname (`model-server:8080`) ကတဆင့်၊ Docker ရဲ့ embedded DNS က ဒီ network ထဲမှာ automatic resolve လုပ်ပေးပါတယ်။

`app` တစ်ခုတည်းကပဲ port တစ်ခု host ဆီ publish လုပ်ပါတယ် (`3000:3000`)၊ container ပြင်ပက ရောက်ဖို့ ရည်ရွယ်ထားတဲ့ piece အမှန်တကယ် ဒါဖြစ်လို့ပါ။

Named volume `model-weights` ဟာ convenience တစ်ခုတည်းထက် ပိုအရေးကြီးတဲ့ အကြောင်းရင်းရှိပါတယ် — ဒါမပါရင် multi-gigabyte model file က container ရဲ့ writable layer ထဲမှာ ရှိနေပြီး၊ `docker rm` ဒါမှမဟုတ် rebuild တစ်ခုက download ကို ပစ်ပယ်ပြီး ပြန် pull ခိုင်းနိုင်ပါတယ်၊ named volume တစ်ခုကတော့ container ရဲ့ lifecycle နဲ့ သီးခြား တည်မြဲပါတယ်။

Docker က Concurrency ကို မဖြေရှင်းပေးပါ

Concurrency နဲ့ queuing ကိုတော့ Docker က လုံးဝ မဖြေရှင်းပေးပါဘူး — model server တစ်ခုကို containerize လုပ်တာက ၎င်းရဲ့ GPU တစ်ခုတည်းက request ဘယ်နှစ်ခု တကယ် parallel run နိုင်လဲဆိုတာကို ပြောင်းမပေးပါဘူး၊ process ကို ဘယ်လို package လုပ်ပြီး network ချိတ်လဲဆိုတာပဲ ပြောင်းပေးပါတယ်။ User များစွာက GPU တစ်ခုတည်းကို share ဖို့ လိုအပ်ရင် model server ကိုယ်တိုင် (ဒါမှမဟုတ် ၎င်းရှေ့က proxy) မှာ real queuing ဒါမှမဟုတ် rate-limiting logic လိုအပ်ပါတယ် — Docker ရဲ့ အလုပ်က isolation နဲ့ reproducible deployment ပါ၊ scheduling မဟုတ်ပါဘူး။

အတူတူ စမ်းရေးကြည့်မယ်

bash
# docker-compose.yml (conceptual shape, not a full working file)
#
# services:
#   model-server:
#     image: ghcr.io/ggml-org/llama.cpp:server
#     command: ["--model", "/models/model.gguf", "--host", "0.0.0.0", "--port", "8080"]
#     volumes:
#       - model-weights:/models
#     networks:
#       - internal
#     # no "ports:" mapping to the host -- not published externally
#
#   app:
#     build: ./app
#     depends_on:
#       - model-server
#     environment:
#       - MODEL_SERVER_URL=http://model-server:8080
#     ports:
#       - "3000:3000"
#     networks:
#       - internal
#
# volumes:
#   model-weights:
#
# networks:
#   internal:

docker network create internal
docker volume create model-weights
docker run -d --name model-server --network internal \
  -v model-weights:/models \
  ghcr.io/ggml-org/llama.cpp:server \
  --model /models/model.gguf --host 0.0.0.0 --port 8080
docker run -d --name app --network internal -p 3000:3000 \
  -e MODEL_SERVER_URL=http://model-server:8080 my-app-image
You should see
Command တွေထဲက ဘယ်ခုမှ ပြသဖို့ အဓိပ္ပာယ်ရှိတဲ့ console output ထုတ်မပေးပါဘူး — `docker network create` နှင့် `docker volume create` တို့က ဖန်တီးလိုက်တဲ့ resource ရဲ့ ID ကို print ပေးပြီး၊ `docker run -d` က container အသစ်ရဲ့ ID ကို print ပေးပြီး detached mode ဖြစ်လို့ ချက်ချင်း return ပြန်ပါတယ်။ စစ်ဆေးသင့်တဲ့ behavioral result ကတော့ print လုပ်ထားတဲ့ text မဟုတ်ဘဲ connectivity ပါ — `app` container အတွင်းက `curl http://model-server:8080` က container နှစ်ခုစလုံး `internal` network ကို မျှဝေထားပြီး Docker ရဲ့ DNS က service name ကို resolve လုပ်ပေးလို့ အောင်မြင်သင့်ပါတယ်။ Host machine ကနေ ဒါမှမဟုတ် LAN ပေါ်က device တခြားကနေ model server ကို port 8080 ပေါ်မှာ တိုက်ရိုက် ရောက်ဖို့ ကြိုးစားရင် connect မရသင့်ပါဘူး၊ ဒီ port ကို host ဆီ ဘယ်တော့မှ publish မလုပ်ခဲ့လို့ပါ — 3000 ကိုပဲ publish လုပ်ခဲ့တာပါ၊ app container အတွက်ပဲလည်း ဖြစ်ပါတယ်။

၅ မိနစ် စမ်းကြည့်

Diagram ထဲ ဖော်ပြထားတဲ့ router logic ကို plain function တစ်ခုအဖြစ် ရေးပါ — request ရဲ့ ခန့်မှန်းထားတဲ့ sensitivity (boolean) နှင့် ခန့်မှန်းထားတဲ့ complexity (low/medium/high) ပေးလိုက်ရင်၊ 'small_local', 'large_local', 'cloud' သုံးခုထဲက ဘယ်ဟာဆီ route လုပ်သင့်လဲ ပြန်ပေးပါ၊ sensitive request ဘယ်ဟာမဆို complexity မည်သို့ဖြစ်စေ local မှာပဲ အမြဲ ကျန်ရစ်ရမယ့် rule ကို လိုက်နာပါ။

သတိလေးတစ်ချက်

'Debug လုပ်ဖို့ ချက်ချင်းပဲ' ဆိုပြီး model server ရဲ့ port ကို host ဆီ publish လုပ်ပြီး mapping ကို ဖြုတ်ဖို့ မေ့သွားခြင်း — app container ကနေပဲ ရောက်နိုင်အောင် ထားချင်တဲ့ ရည်ရွယ်ချက်တစ်ခုလုံးကို တိတ်တဆိတ် ချေဖျက်ပစ်ပါတယ်။

GPU share ထားတဲ့ request queuing ဒါမှမဟုတ် concurrency limit ကို Docker ကိုယ်တိုင် ပေးမယ်လို့ ယူဆခြင်း — model server တစ်ခုကို containerize လုပ်တာက package နှင့် networking ကိုပဲ ပြောင်းပေးပါတယ်၊ ၎င်းရဲ့ GPU က တကယ် request ဘယ်နှစ်ခု တစ်ပြိုင်နက် run နိုင်လဲဆိုတာကို ပြောင်းမပေးပါဘူး။

Docker Docs — NetworkingLocal AI / Local LLM

ဒီနေရာမှာ လူအများမှားတတ်တယ်

  • 'Debug လုပ်ဖို့ ချက်ချင်းပဲ' ဆိုပြီး model server ရဲ့ port ကို host ဆီ publish လုပ်ပြီး mapping ကို ဖြုတ်ဖို့ မေ့သွားခြင်း — app container ကနေပဲ ရောက်နိုင်အောင် ထားချင်တဲ့ ရည်ရွယ်ချက်တစ်ခုလုံးကို တိတ်တဆိတ် ချေဖျက်ပစ်ပါတယ်။
  • GPU share ထားတဲ့ request queuing ဒါမှမဟုတ် concurrency limit ကို Docker ကိုယ်တိုင် ပေးမယ်လို့ ယူဆခြင်း — model server တစ်ခုကို containerize လုပ်တာက package နှင့် networking ကိုပဲ ပြောင်းပေးပါတယ်၊ ၎င်းရဲ့ GPU က တကယ် request ဘယ်နှစ်ခု တစ်ပြိုင်နက် run နိုင်လဲဆိုတာကို ပြောင်းမပေးပါဘူး။
  • Model (သို့) tool အသစ်တစ်ခုကို production/daily-use workflow ထဲ တိုက်ရိုက်မထည့်ခင် သေးငယ်တဲ့ scale နဲ့ အရင်စမ်းကြည့်ပါ။

လေ့ကျင့်ခန်း

Diagram ထဲ ဖော်ပြထားတဲ့ router logic ကို plain function တစ်ခုအဖြစ် ရေးပါ — request ရဲ့ ခန့်မှန်းထားတဲ့ sensitivity (boolean) နှင့် ခန့်မှန်းထားတဲ့ complexity (low/medium/high) ပေးလိုက်ရင်၊ 'small_local', 'large_local', 'cloud' သုံးခုထဲက ဘယ်ဟာဆီ route လုပ်သင့်လဲ ပြန်ပေးပါ၊ sensitive request ဘယ်ဟာမဆို complexity မည်သို့ဖြစ်စေ local မှာပဲ အမြဲ ကျန်ရစ်ရမယ့် rule ကို လိုက်နာပါ။

You'll know it worked when: Command တွေထဲက ဘယ်ခုမှ ပြသဖို့ အဓိပ္ပာယ်ရှိတဲ့ console output ထုတ်မပေးပါဘူး — `docker network create` နှင့် `docker volume create` တို့က ဖန်တီးလိုက်တဲ့ resource ရဲ့ ID ကို print ပေးပြီး၊ `docker run -d` က container အသစ်ရဲ့ ID ကို print ပေးပြီး detached mode ဖြစ်လို့ ချက်ချင်း return ပြန်ပါတယ်။ စစ်ဆေးသင့်တဲ့ behavioral result ကတော့ print လုပ်ထားတဲ့ text မဟုတ်ဘဲ connectivity ပါ — `app` container အတွင်းက `curl http://model-server:8080` က container နှစ်ခုစလုံး `internal` network ကို မျှဝေထားပြီး Docker ရဲ့ DNS က service name ကို resolve လုပ်ပေးလို့ အောင်မြင်သင့်ပါတယ်။ Host machine ကနေ ဒါမှမဟုတ် LAN ပေါ်က device တခြားကနေ model server ကို port 8080 ပေါ်မှာ တိုက်ရိုက် ရောက်ဖို့ ကြိုးစားရင် connect မရသင့်ပါဘူး၊ ဒီ port ကို host ဆီ ဘယ်တော့မှ publish မလုပ်ခဲ့လို့ပါ — 3000 ကိုပဲ publish လုပ်ခဲ့တာပါ၊ app container အတွက်ပဲလည်း ဖြစ်ပါတယ်။

Serving, Docker နှင့် Hybrid Architecture | Thuta Learning