<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ebube Okeke</title>
    <description>The latest articles on DEV Community by Ebube Okeke (@divine_ebube).</description>
    <link>https://dev.to/divine_ebube</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4042689%2Fce394471-08db-463d-8257-044e93bc8120.png</url>
      <title>DEV Community: Ebube Okeke</title>
      <link>https://dev.to/divine_ebube</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/divine_ebube"/>
    <language>en</language>
    <item>
      <title>Serve your own ChatGPT. How to Serve Inference from an Ops Perspective</title>
      <dc:creator>Ebube Okeke</dc:creator>
      <pubDate>Thu, 23 Jul 2026 00:36:43 +0000</pubDate>
      <link>https://dev.to/divine_ebube/serve-your-own-chatgpt-how-to-serve-inference-from-an-ops-perspective-bbi</link>
      <guid>https://dev.to/divine_ebube/serve-your-own-chatgpt-how-to-serve-inference-from-an-ops-perspective-bbi</guid>
      <description>&lt;h3&gt;
  
  
  Self-Managed LLM Inference on Kubernetes — Part I: Deploying a Highly Available Inference Server
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Background
&lt;/h4&gt;

&lt;p&gt;In a world where LLMs have reshaped multiple industries, many enterprises are quickly realizing that this has come to stay. Closed models from frontier labs have driven innovation to the edge of possibility, and AI adoption has come at a pace some might consider unprecedented.&lt;/p&gt;

&lt;p&gt;This development, however, has created its own set of new challenges that need to be solved — &lt;strong&gt;one&lt;/strong&gt; of which is data privacy and sovereignty: how do you prevent handing your data over to frontier model providers to improve their models further and sell it back to you at a higher/lower token output cost?&lt;/p&gt;

&lt;p&gt;At the same time, significant progress has been made in self-managed ML pipelines, from model training to inference. A few years ago (centuries ago in AI time), training models were thought to be the obvious challenge, with inference taking a back seat — maybe understandably so. But now, with the plethora of very capable closed and open models, inference became the million (maybe billion) dollar problem, and the market has reacted accordingly.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;Kubernetes&lt;/strong&gt;: developed by Google initially (Borg), Kubernetes (k8s) has morphed to become perhaps the backbone of modern application infrastructure. The maintainers, with their foresight, have made AI objects first-class citizens. Given the massive and rapid improvements made over the years, ML workflows and specifically inference has become easier to democratize — well, as long as you can afford the GPUs to serve requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Series Overview
&lt;/h2&gt;

&lt;p&gt;In this multi-part article I will be demonstrating and condensing some insight in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploying inference workloads in a Highly Available (HA) setting&lt;/li&gt;
&lt;li&gt;Enhancing models (specialists) using LoRA adapters and the challenges surrounding scaling, observability, and governance, some of which include:

&lt;ul&gt;
&lt;li&gt;Respecting regulations&lt;/li&gt;
&lt;li&gt;Curating data&lt;/li&gt;
&lt;li&gt;Creating fact sheets&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of which are of extreme importance in highly regulated industries, as quoted from an IBM Expert:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Source attribution is the new explainability.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Run Your Own Inference?
&lt;/h2&gt;

&lt;p&gt;Why would I run my own inference, and not just use a frontier model, you might ask? Well, good question. Here are just some reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Your data belongs to you&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Air-gapped environments&lt;/strong&gt; — your infra can be run in air-gapped environments used by governments and offshore environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier compliance&lt;/strong&gt; — makes compliance and regulations easier to carry out&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret containment&lt;/strong&gt; — your users/employees can paste secrets/proprietary data (yes, they still do, and yes, it's still not advised), and you will be at peace knowing that your company secrets are not being used to train a general model somewhere in the cloud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better performance&lt;/strong&gt; — when fine-tuned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster response time&lt;/strong&gt; — in pre-fill and decode stages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-effectiveness&lt;/strong&gt; — for the right use case, it can actually be more cost-effective&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rather than having a single model that tries to solve problems for all industries — thereby making you pay for that overhead — you can have your own model fine-tuned for your use case. Yes, it will not be Fable or Sol level, but it will be just as good and even better for you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fine-tuning will be covered in a future article. But how do you run the foundation model in the first place?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What You'll Learn in Part I
&lt;/h2&gt;

&lt;p&gt;By the end of Part I, you will see how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take a raw model and make it accept prompts, from both raw API requests and from the UI&lt;/li&gt;
&lt;li&gt;Create a Grafana Dashboard to monitor model performance&lt;/li&gt;
&lt;li&gt;Load test using Grafana K6s&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl70qggdjye8nkh5e57ag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl70qggdjye8nkh5e57ag.png" alt="Chat UI" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwrd845zrj84ov33c9dz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhwrd845zrj84ov33c9dz.png" alt="KV Cache Usage" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure&lt;/td&gt;
&lt;td&gt;Kubernetes cluster (GKE Autopilot), NVIDIA L4 GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inference Server&lt;/td&gt;
&lt;td&gt;vLLM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;&lt;code&gt;google/gemma-2-2b-it&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kube-prometheus-operator&lt;/code&gt; Helm chart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;Hugging Face API token (for pulling model weights)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;This is not a step-by-step guide — it shows roughly all the components needed to stand up an inference server.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Spin Up a Cluster
&lt;/h3&gt;

&lt;p&gt;Use the Google Cloud Console or an IaC tool like Terraform to bootstrap a Kubernetes cluster. This is where your model will be deployed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Create Your Model Deployment
&lt;/h3&gt;

&lt;p&gt;Create a deployment using the vLLM inference server image, passing your model in the args. On the Google Cloud Console, you can directly deploy your model, which automatically generates the manifest with the right annotations. You can then pull this and modify it as you see fit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure to create a Kubernetes Secret containing your Hugging Face API token if you're pulling the model from Hugging Face.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Example Model Deployment YAML
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inference-with-lora&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemma-2-2b-it-vllm-inference-server&lt;/span&gt;
    &lt;span class="na"&gt;examples.ai.gke.io/applied-by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gke-aiml-ui&lt;/span&gt;
    &lt;span class="na"&gt;examples.ai.gke.io/deployment-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inference-XgDD8&lt;/span&gt;
    &lt;span class="na"&gt;examples.ai.gke.io/deployment-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aire&lt;/span&gt;
    &lt;span class="na"&gt;recommender.ai.gke.io/generated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
    &lt;span class="na"&gt;recommender.ai.gke.io/inference-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vllm&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemma-2-2b-it-vllm-inference-server&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RollingUpdate&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxUnavailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;25%&lt;/span&gt;
      &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;25%&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ai.gke.io/inference-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vllm&lt;/span&gt;
        &lt;span class="na"&gt;ai.gke.io/model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemma-2-2b-it&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gemma-2-2b-it-vllm-inference-server&lt;/span&gt;
        &lt;span class="na"&gt;examples.ai.gke.io/applied-by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gke-aiml-ui&lt;/span&gt;
        &lt;span class="na"&gt;examples.ai.gke.io/deployment-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inference-XgDD8&lt;/span&gt;
        &lt;span class="na"&gt;examples.ai.gke.io/deployment-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aire&lt;/span&gt;
        &lt;span class="na"&gt;examples.ai.gke.io/source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blueprints&lt;/span&gt;
        &lt;span class="na"&gt;recommender.ai.gke.io/generated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
        &lt;span class="na"&gt;recommender.ai.gke.io/inference-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vllm&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# ------------------------------------------------------------------ #&lt;/span&gt;
      &lt;span class="c1"&gt;# Init container: pre-pull LoRA adapters from HF Hub into shared vol  #&lt;/span&gt;
      &lt;span class="c1"&gt;# so vLLM can load them from disk rather than fetching at request time #&lt;/span&gt;
      &lt;span class="c1"&gt;# ------------------------------------------------------------------ #&lt;/span&gt;
      &lt;span class="na"&gt;initContainers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull-lora-adapters&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python:3.11-slim&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/bin/sh&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
              &lt;span class="s"&gt;pip install -q huggingface_hub &amp;amp;&amp;amp; \&lt;/span&gt;
              &lt;span class="s"&gt;python3 - &amp;lt;&amp;lt;'EOF'&lt;/span&gt;
              &lt;span class="s"&gt;from huggingface_hub import snapshot_download&lt;/span&gt;
              &lt;span class="s"&gt;import os&lt;/span&gt;
              &lt;span class="s"&gt;token = os.environ["HUGGING_FACE_HUB_TOKEN"]&lt;/span&gt;

              &lt;span class="s"&gt;# Medical adapter — swap in your preferred adapter here&lt;/span&gt;
              &lt;span class="s"&gt;snapshot_download(&lt;/span&gt;
                &lt;span class="s"&gt;repo_id="azizdeniz890/gemma-medical-qa-lora",&lt;/span&gt;
                &lt;span class="s"&gt;local_dir="/adapters/medical",&lt;/span&gt;
                &lt;span class="s"&gt;token=token,&lt;/span&gt;
              &lt;span class="s"&gt;)&lt;/span&gt;

              &lt;span class="s"&gt;# Add more adapters below as needed, e.g.:&lt;/span&gt;
              &lt;span class="s"&gt;# snapshot_download(&lt;/span&gt;
              &lt;span class="s"&gt;#   repo_id="your-org/gemma-2-legal-lora",&lt;/span&gt;
              &lt;span class="s"&gt;#   local_dir="/adapters/legal",&lt;/span&gt;
              &lt;span class="s"&gt;#   token=token,&lt;/span&gt;
              &lt;span class="s"&gt;# )&lt;/span&gt;
              &lt;span class="s"&gt;EOF&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HUGGING_FACE_HUB_TOKEN&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inference-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hf_api_token&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adapters&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/adapters&lt;/span&gt;

      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inference-server&lt;/span&gt;
          &lt;span class="c1"&gt;# Bumped from v0.7.2 — v0.8.x+ has stable LoRA hub resolver +&lt;/span&gt;
          &lt;span class="c1"&gt;# runtime updating support. Pin to a specific version in prod.&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vllm/vllm-openai:v0.8.5&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;metrics&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8000&lt;/span&gt;
              &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;python3&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-m&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vllm.entrypoints.openai.api_server&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--model=$(MODEL_ID)&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--disable-log-requests&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--tensor-parallel-size=1&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--max-num-seq=1024&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--num-scheduler-steps=8&lt;/span&gt;
            &lt;span class="c1"&gt;# ---- LoRA flags -------------------------------------------- #&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--enable-lora&lt;/span&gt;
            &lt;span class="c1"&gt;# Must be &amp;gt;= the rank of any adapter you load.&lt;/span&gt;
            &lt;span class="c1"&gt;# azizdeniz890/gemma-medical-qa-lora uses rank 16.&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--max-lora-rank=64&lt;/span&gt;
            &lt;span class="c1"&gt;# How many adapters to keep hot in GPU memory simultaneously.&lt;/span&gt;
            &lt;span class="c1"&gt;# Increase if you add more adapters; each costs ~rank * hidden_dim MB.&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--max-loras=4&lt;/span&gt;
            &lt;span class="c1"&gt;# Keeps adapter weights in CPU memory between requests so they&lt;/span&gt;
            &lt;span class="c1"&gt;# don't need to be re-fetched from disk every time.&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--max-cpu-loras=8&lt;/span&gt;
            &lt;span class="c1"&gt;# ------------------------------------------------------------ #&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MODEL_ID&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google/gemma-2-2b-it&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HUGGING_FACE_HUB_TOKEN&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inference-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hf_api_token&lt;/span&gt;
            &lt;span class="c1"&gt;# ---- LoRA runtime env vars ---------------------------------- #&lt;/span&gt;
            &lt;span class="c1"&gt;# Allow adapters to be loaded/swapped without restarting vLLM&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VLLM_ALLOW_RUNTIME_LORA_UPDATING&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
            &lt;span class="c1"&gt;# Tell vLLM to look in the mounted /adapters directory first&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VLLM_PLUGINS&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lora_filesystem_resolver&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VLLM_LORA_RESOLVER_CACHE_DIR&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/adapters&lt;/span&gt;
            &lt;span class="c1"&gt;# ------------------------------------------------------------ #&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
          &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/health&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8000&lt;/span&gt;
            &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;   &lt;span class="c1"&gt;# was 0 — give vLLM time to load model&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
            &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
            &lt;span class="na"&gt;successThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
            &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dshm&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dev/shm&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adapters&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/adapters&lt;/span&gt;
              &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dshm&lt;/span&gt;
          &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Memory&lt;/span&gt;
        &lt;span class="c1"&gt;# Shared volume between init container (writer) and main container (reader)&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adapters&lt;/span&gt;
          &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;

      &lt;span class="na"&gt;nodeSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cloud.google.com/gke-accelerator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nvidia-l4&lt;/span&gt;
        &lt;span class="na"&gt;cloud.google.com/gke-accelerator-count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The &lt;code&gt;annotations&lt;/code&gt; and &lt;code&gt;nodeSelector&lt;/code&gt; fields regarding accelerator selection are critical — the pod will not be scheduled on GKE nodes without them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This deployment has LoRA enabled with a built-in adapter; it will be used for the fine-tuned demo in the next part of the series.&lt;/p&gt;

&lt;p&gt;Create a k8s service that proxies traffic for the deployment, and port-forward it for quick testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/inference-service 8000:8080 &lt;span class="nt"&gt;-n&lt;/span&gt; default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send a curl request to the /v1/completions endpoint to quickly send a test prompt&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/v1/completions &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
        "model": "google/gemma-2-2b-it",
        "prompt": "Why is the plant green?",
        "max_tokens": 512
    }'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;For observability, you can use the readily available &lt;code&gt;kube-prometheus-operator&lt;/code&gt; community chart, which comes with predefined dashboards. However, many of those dashboards will not be relevant as they measure CPU workload metrics — the GPU is primarily being used to serve the model — so you will have to create your own dashboards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsfvhuejpbp7unfc41azf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsfvhuejpbp7unfc41azf.png" alt="High Level Observability Architecture" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bit on the Metrics
&lt;/h2&gt;

&lt;p&gt;So you have deployed your model, and you send requests to your server getting your prompt response; now how do you actually measure how well your deployment works and compare it to others? Some of the relevant metrics, specific to the two stages of inference — prefill and decode — are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Full Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTFT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Time to First Token&lt;/td&gt;
&lt;td&gt;How long it takes to get the first token response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tokens/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tokens per Second&lt;/td&gt;
&lt;td&gt;How many tokens your model can output per second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;E2E Request Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;End-to-End Request Latency&lt;/td&gt;
&lt;td&gt;How long it takes from sending your prompt to the last token being returned by the model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Avg Queue Wait Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Average Queue Wait Time&lt;/td&gt;
&lt;td&gt;How long requests are being queued before being processed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Coming up Next
&lt;/h2&gt;

&lt;p&gt;Now you are up and running, but you want to run your fine-tuned model alongside the base model; this is what we will discuss in the next part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thank You
&lt;/h2&gt;

&lt;p&gt;If there's any topic you will want me to discuss next, add it to the comments and I will take a look&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshot dump
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhd506e9zotpot8o5vv7o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhd506e9zotpot8o5vv7o.png" alt="Google Cloud Console" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>infrastructure</category>
      <category>machinelearning</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
