RPC Kullanarak CUDA + SYCL ile LLM Çıkarımı


RPC Kullanarak CUDA + SYCL ile LLM Çıkarımı

Giriş:

Eski masaüstü bilgisayarımı sanallaştırma vb. deneyler için kullanıyorum. Bir nevi aklıma gelen şeyleri yapabileceğim bir çukur gibi, attığınız yükleri kabul edip iyi-kötü bir şekilde çalıştırıyor. Bu makine bir arkadaşım köşede yatan CPUsunu vermesi ile ile 8 çekirdek 16 threadden, 16 çekirdek 32 threade çıktı, 64GB DDR4 Ram mevcut ve birkaç NVME takılı, üzerinde muhtelif bluetoothlar falan mevcut... Ayrıca bunlara ek olarak üzerinde bir Nvdia 1080TI ve bir de Intel A770 var, tam bir frankenstein. Üzerindeki PCI popülasyonu yoğunluğundan dolayı bu GPUlar da çok verimli çalışıyor diyemeyiz ama istediklerimizi yapacak kadar çalışıyorlar. Bu makinenin üzerine birçok VM attım bunlardan 3ü OpenStack için kullanılıyor. 1 adet controller 2 adet compute şeklinde. Storage tarafında da bir NFS VM mevcut, bunu da o veya bu şekilde OpenStack'e bağladım. Söz konusu gpuları OpenStack'e paslamaya gelince herşey iç-içe. Yani A770 bir VM e paslanıyor, oradan OpenStack kendi oluşturduğu VMe paslıyor... Ana sunucu üzerinde biraz IOMMU kavgası, el ile virsh xml editleyince çalışıyor. Yeterince dövünce çalışıyor da diyebiliriz. İşte öyle bir hikaye.

Malum 1080TI 11GB vram ile A770 de 16GB vram ile geliyor (8GBleri de mevcut herhalde). Bir taraf CUDA koşarken bir taraf oneapi, sycl vs. koşabiliyor. Aslında totalde 27GB vram mevcut, fakat bunları "total" olarak kullanmak mümkünmü onu denemek istedim, maksat performans değil maksat muhabbet.

Öncelikle gpuların pci link hızlarını vereyim, beklentiyi biraz aşağıya çekelim.

root@virtbase:~# lspci | grep -i vga
0c:00.0 VGA compatible controller: Intel Corporation DG2 [Arc A770] (rev 08)
0e:00.0 VGA compatible controller: NVIDIA Corporation GP102 [GeForce GTX 1080 Ti] (rev a1)

root@virtbase:~# lspci -vv -s 0c:00.0 | grep -E "LnkCap:|LnkSta:"
        LnkCap: Port #0, Speed 2.5GT/s, Width x1, ASPM L0s L1, Exit Latency L0s <64ns, L1 <1us
        LnkSta: Speed 2.5GT/s, Width x1

root@virtbase:~# lspci -vv -s 0e:00.0 | grep -E "LnkCap:|LnkSta:"
        LnkCap: Port #2, Speed 8GT/s, Width x16, ASPM L0s L1, Exit Latency L0s <512ns, L1 <4us
        LnkSta: Speed 2.5GT/s (downgraded), Width x4 (downgraded)

Amacım bir llama.cpp ile RPC sunucu kaldırmak, bu sunucunun 1080TI ve A770 e bir modeli paylaştırmasını sağlamak (Bakınız: Tensor parallelism, Pipeline parallelism). Bunun için de ana sunucuya ek olarak yine llama.cpp üzerinden CUDA koşan 1080TI ile çalışan bir RPC sunucu, SYCL koşan A770 ile çalışan bir RPC sunucu kaldıracağız. Ana sunucu da bunlara gerekli dağıtımı sağlayacak.

Fikir güzel, işlermi belki. İsimler, jargonlar havada uçuşur. Llm-Hetero, Rpc-Hibrit Llm Inference... Ya tutarsa diyip girişiyoruz.

Gelişme:

Altapım OpenStack Gazpacho, bunun üzerinde Vexxhost Magnum Cluster Api driveri ile OpenStack Magnum koşuyorum, tabi ek olarak Octavia falan da mevcut. Bu Magnum ile de kendi oluşturduğum imajlar üzerinden kubernetes kümeleri kaldırıyorum. Kubernetes versiyon 1.36.2.

Kubernetes kümemizde durum şöyle, 1 master, 1 1080TI worker ve 1 A770 worker.

erdem@EWUL13:~$ kubectl get nodes
NAME                                             STATUS   ROLES                  AGE   VERSION
kube-ngar4-jn72v-c4c7h                           Ready    control-plane,master   20h   v1.36.2
kube-ngar4-worker-gpu-1080ti-mnvpd-hhh6v-6klk6   Ready    worker                 20h   v1.36.2
kube-ngar4-worker-gpu-a770-mrzqm-6r6xc-h5wl9     Ready    worker                 20h   v1.36.2

Worker düğümlerimizde gerekli sürücüler tarafımca host seviyesinde yüklendi, 1080TI terk edilmiş bir kart olduğu için Nvidia Sürücüsü versiyonu 580, Cuda versiyonu ise 12.9.2. A770 de ise durum biraz farklı, gerekli sürücü aslında kernel içerisinde bile mevcut (Herhalde 6+ sonrası, i915 ve Xe gömülü geliyor, HWE kernel ile de karıştırıyor olabilirim). Fakat benim gibi kendi düğüm imajlarınızı kendiniz oluşturuyor ve boyuttan tasarruf etmek için herşeyi buduyorsanız dikkat edin. A770 sürücüleri yüklü olmasına rağmen gerekli firmwareleri el ile kopyalamanız gerekebilir sonra initramfs güncellemesi falan...

sudo wget -P /lib/firmware/i915 "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/i915/dg2_guc_70.bin"
sudo wget -P /lib/firmware/i915 "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/i915/dg2_dmc_ver2_08.bin"

Neyse lafı uzatmadan Dockerfile ve RPC sunucularını paylaşalım.

Cuda için Dockerfile:

# ---- build
FROM nvidia/cuda:12.9.2-devel-ubuntu24.04 AS build

RUN apt-get update && apt-get install -y --no-install-recommends git build-essential cmake ninja-build && rm -rf /var/lib/apt/lists/*

WORKDIR /build
RUN git clone --depth 1 https://github.com/ggml-org/llama.cpp.git .
RUN cmake -B build -G Ninja -DGGML_CUDA=ON -DGGML_RPC=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXE_LINKER_FLAGS=-Wl,--allow-shlib-undefined && cmake --build build --target ggml-rpc-server -j"$(nproc)"
RUN ldd build/bin/ggml-rpc-server
# ---- runtime 
FROM nvidia/cuda:12.9.2-runtime-ubuntu24.04

RUN apt-get update && apt-get install -y --no-install-recommends libgomp1 && rm -rf /var/lib/apt/lists/*

COPY --from=build /build/build/bin/ggml-rpc-server /usr/local/bin/ggml-rpc-server
COPY --from=build /build/build/bin/*.so* /usr/local/lib/
RUN ldconfig

ENTRYPOINT ["/usr/local/bin/ggml-rpc-server"]
CMD ["-H", "0.0.0.0", "-p", "50052"]

SYCL için Dockerfile (Runtime imajında çalıştıramadım, çok uğraşamadım 4.4GB lık bir canavar çıktı)

# ---- build
FROM intel/oneapi-basekit:2025.3.2-0-devel-ubuntu24.04 AS build

RUN apt-get update && apt-get install -y --no-install-recommends git cmake ninja-build && rm -rf /var/lib/apt/lists/*

WORKDIR /build
RUN git clone --depth 1 https://github.com/ggml-org/llama.cpp.git .

#SHELL ["/bin/bash", "-c"]

RUN source /opt/intel/oneapi/setvars.sh --force && cmake -B build -G Ninja -DGGML_SYCL=ON -DGGML_RPC=ON -DCMAKE_C_COMPILER=icx -DCMAKE_CXX_COMPILER=icpx -DCMAKE_BUILD_TYPE=Release && cmake --build build --target ggml-rpc-server -j"$(nproc)"
RUN ldd build/bin/ggml-rpc-server
# ---- runtime
FROM intel/oneapi-basekit:2025.3.2-0-devel-ubuntu24.04

RUN apt-get update && apt-get install -y --no-install-recommends libgomp1 && rm -rf /var/lib/apt/lists/*

COPY --from=build /build/build/bin/ggml-rpc-server /usr/local/bin/ggml-rpc-server
COPY --from=build /build/build/bin/*.so* /usr/local/lib/
RUN ldconfig

COPY entrypoint-sycl.sh /usr/local/bin/entrypoint-sycl.sh
RUN chmod +x /usr/local/bin/entrypoint-sycl.sh

ENTRYPOINT ["/usr/local/bin/entrypoint-sycl.sh"]
CMD ["-H", "0.0.0.0", "-p", "50052"]

A770 için entrypoint (ZES_ENABLE_SYSMAN i nerede sağlarsam sağlayım loga döktüğü warningi aşamadım. xpu-smi ile de GPU Util değerini sorunsuz ve düzgün bir şekilde alamadım)

export ZES_ENABLE_SYSMAN=1

source /opt/intel/oneapi/setvars.sh --force
exec /usr/local/bin/ggml-rpc-server "$@"

Ana sunucu için RPC Dockerfile

FROM ubuntu:24.04 AS build

RUN apt-get update && apt install -y --reinstall ca-certificates && update-ca-certificates && apt-get install -y --no-install-recommends git build-essential cmake ninja-build && rm -rf /var/lib/apt/lists/*

WORKDIR /build
RUN git clone --depth 1 https://github.com/ggml-org/llama.cpp.git .

RUN cmake -B build -G Ninja -DGGML_RPC=ON -DCMAKE_BUILD_TYPE=Release && cmake --build build --target llama-server -j"$(nproc)"
# ---- runtime image ----
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y --no-install-recommends libgomp1 ca-certificates curl && rm -rf /var/lib/apt/lists/*

COPY --from=build /build/build/bin/llama-server /usr/local/bin/llama-server
COPY --from=build /build/build/bin/*.so* /usr/local/lib/
RUN ldconfig

ENTRYPOINT ["/usr/local/bin/llama-server"]

Bunları paketleyince sadece ayağa kaldırmak kalıyor. Bunları biraz kısaltarak paylaşayım yazı fazla uzamasın.

CUDA:

...
containers:
        - name: rpc-server
          image: kendi-repo-adresin/rpc:cuda
          args: ["-H", "0.0.0.0", "-p", "50052"]
          ports:
            - containerPort: 50052
              name: rpc
          resources:
            limits:
              nvidia.com/gpu: 1
            ...
            requests:
              nvidia.com/gpu: 1
            ...
---
apiVersion: v1
kind: Service
metadata:
  name: rpc-cuda
  namespace: llm-hetero
  labels:
    app: rpc-cuda
spec:
  clusterIP: None   # headless — single pod, stable DNS: rpc-cuda.llm-hetero.svc.cluster.local
  selector:
    app: rpc-cuda
  ports:
    - port: 50052
      targetPort: 50052
      name: rpc

SYCL: (GGML_SYCL_ENABLE_OPT için kesinlikle şu linke bakın )

...
containers:
        - name: rpc-server
          image: kendi-repo-adresin/rpc:sycl
          args: ["-H", "0.0.0.0", "-p", "50052"]
          env:
            - name: GGML_SYCL_ENABLE_OPT
              value: "0"
        ...
          ports:
            - containerPort: 50052
              name: rpc
          resources:
            limits:
              gpu.intel.com/i915: 1
            ...
            requests:
              gpu.intel.com/i915: 1
            ...
---
apiVersion: v1
kind: Service
metadata:
  name: rpc-sycl
  namespace: llm-hetero
  labels:
    app: rpc-sycl
spec:
  clusterIP: None   # headless — single pod, stable DNS: rpc-sycl.llm-hetero.svc.cluster.local
  selector:
    app: rpc-sycl
  ports:
    - port: 50052
      targetPort: 50052
      name: rpc

ANA: (Ana Sunucuda modeli kendi lokal ağım üzerinden indiriyorum, persisten volume e yazılıyor) Ek olarak load-balancer kısmımız var ki OpenStack tarafında loadbalancer oluşsun ve biz arayüze düşebilelim.

...
initContainers:
  - name: fetch-model
    image: curlimages/curl:8.9.1
    envFrom:
      - configMapRef:
          name: model-source
    command:
      - sh
      - -c
      - |
        if [ ! -f /models/model2.gguf ]; then
          echo "Downloading $MODEL_URL"
          curl -fL --retry 3 --retry-delay 5 "$MODEL_URL" -o /models/model2.gguf.tmp
          mv /models/model2.gguf.tmp /models/model2.gguf
        else
          echo "Model already cached"
        fi
    volumeMounts:
      - name: model-storage
        mountPath: /models
containers:
  - name: llama-server
    image: kendi-repo-adresin/rpc:main
    args:
      - "-m"
      - "/models/model2.gguf"
      - "--host"
      - "0.0.0.0"
      - "--port"
      - "8080"
      - "-ngl"
      - "99"
      - "--rpc"
      - "rpc-sycl.llm-hetero.svc.cluster.local:50052,rpc-cuda.llm-hetero.svc.cluster.local:50052"
      - "-c"
      - "131072"
    ports:
      - containerPort: 8080
        name: http
---
apiVersion: v1
kind: Service
metadata:
  name: main-server
  namespace: llm-hetero
  labels:
    app: main-server
spec:
  type: LoadBalancer
  selector:
    app: main-server
  ports:
    - port: 8080
      targetPort: 8080
      name: http

Bunlarla beraber yeterince bekleyerek podlar yerine oturuyor.

Sonuç:

Gelelim sonuca, Qwen3.6-35B-A3B-Q4_K_M.gguf modeli için 131072 context size ile bütün layerları gpulara atabiliyoruz. Başlangıç olarak 1080TI tarafında 9700 MB, A770 tarafında 13600 MB vram kullanımı mevcut. Total 27 GB vram üzerinden 22.75GB mertebesine ulaştık sadece başlangıç ile diyeibliriz.

1080TI Nvidia-Smi:

...
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|   0  NVIDIA GeForce GTX 1080 Ti     On  |   00000000:00:05.0 Off |                  N/A |
|  0%   38C    P8             14W /  320W |    9751MiB /  11264MiB |      0%      Default |
|                                         |                        |                  N/A |
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|    0   N/A  N/A            9813      C   /usr/local/bin/ggml-rpc-server         9746MiB |

A770 xpu-smi:

root@kube-ngar4-worker-gpu-a770-mrzqm-6r6xc-h5wl9:~# xpu-smi dump --metrics 1,18 --interval 2
DeviceId    power.draw (W)    memory.used (MiB)
0             36.18       13662.97265625

Peki bunca emeğe karşın gerekli token saniyeyi üretebildik mi. Tabi ki hayır. A770 gpu utilizasyonunda verdiği değerler doğru ise %40 ş geçemedi hiçbir zaman, 1080TI ise %12 utilizasyona kadar nadir yükseldi. Yani sadece layerları gpu yükleyebilmiş oldu, GPU nun kendi faydasını göremedik bile. Bakınız aşağıda sonuçları veriyorum.

7.49.302.746 I srv    load_model: initializing, n_slots = 4, n_ctx_slot = 131072, kv_unified = 'true'
7.49.352.225 I srv          init: chat template supports preserving reasoning, consider enabling it via --reasoning-preserve
7.49.352.287 I srv  llama_server: model loaded
7.49.352.292 I srv  llama_server: listening on http://0.0.0.0:8080
87.11.318.373 I slot get_availabl: id  3 | task -1 | selected slot by LRU, t_last = -1
87.11.318.534 I slot launch_slot_: id  3 | task 0 | processing task, is_child = 0
87.26.895.011 I slot print_timing: id  3 | task 0 | n_decoded =    100, tg =   8.41 t/s, tg_3s =   8.41 t/s
87.29.986.842 I slot print_timing: id  3 | task 0 | n_decoded =    124, tg =   8.28 t/s, tg_3s =   7.76 t/s
87.33.035.898 I slot print_timing: id  3 | task 0 | n_decoded =    150, tg =   8.32 t/s, tg_3s =   8.53 t/s
87.36.063.507 I slot print_timing: id  3 | task 0 | n_decoded =    174, tg =   8.26 t/s, tg_3s =   7.93 t/s
87.39.228.127 I slot print_timing: id  3 | task 0 | n_decoded =    191, tg =   7.89 t/s, tg_3s =   5.37 t/s
87.42.282.798 I slot print_timing: id  3 | task 0 | n_decoded =    207, tg =   7.59 t/s, tg_3s =   5.24 t/s
87.45.392.313 I slot print_timing: id  3 | task 0 | n_decoded =    225, tg =   7.40 t/s, tg_3s =   5.79 t/s
87.48.422.879 I slot print_timing: id  3 | task 0 | n_decoded =    246, tg =   7.36 t/s, tg_3s =   6.93 t/s

Aynı modeli laptopta 4080mobil ile sadece 10 layer GPU üzerinde olacak şekilde denediğimde ise 18 token saniye gibi değerler görebildim. Peki bunu niye denedin o zaman dersiniz, amacım öncelikle çalışacak mı onu görmekti, sonrasında ise gerçekten bir modeli dağıtarak anca çalışabilir hale getireceksek RPC buna yarayacak mı onu anlamaktı. RPC ile bu yapılabiliyor, böylelikle birçok GPU bulunduran sunucuyu birbirine bağlayarak TCP üzerinden bu modelleri o veya bu şekilde çalışabilir hale getirebiliriz, bunu görmüş yolduk. Yani amacıma ulaşabildim mi evet. Bu token hızı ile reasonin yapan modelin sonucunu beklermiyiz, bence hayır.

Referanslar:

Teşekkür:

Ana fotoğraf: Frames For Your Heart orijinaline Unsplash üzerinden ulaşabilirsiniz.

Previous