<?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: Yash Sharma</title>
    <description>The latest articles on DEV Community by Yash Sharma (@yashsharam_f).</description>
    <link>https://dev.to/yashsharam_f</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%2F3913493%2F5a992fae-1fda-41cd-972c-c8f79ee7a84b.jpg</url>
      <title>DEV Community: Yash Sharma</title>
      <link>https://dev.to/yashsharam_f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yashsharam_f"/>
    <language>en</language>
    <item>
      <title>When is Serverless Inference Cheaper than Your Self Hosted GPU? I Benchmarked gpt-oss-120b on Both</title>
      <dc:creator>Yash Sharma</dc:creator>
      <pubDate>Tue, 23 Jun 2026 14:57:47 +0000</pubDate>
      <link>https://dev.to/digitalocean/when-is-serverless-inference-cheaper-than-your-self-hosted-gpu-i-benchmarked-gpt-oss-120b-on-both-4bla</link>
      <guid>https://dev.to/digitalocean/when-is-serverless-inference-cheaper-than-your-self-hosted-gpu-i-benchmarked-gpt-oss-120b-on-both-4bla</guid>
      <description>&lt;p&gt;If you run LLM inference in production, you eventually will ask yourself, should you rent a GPU and run the model yourself, or do you use a serverless API and pay per token? Everyone has an opinion. Far fewer people show you the actual numbers that decide it.&lt;/p&gt;

&lt;p&gt;So I ran both. I put the same model, &lt;a href="https://www.digitalocean.com/community/tutorials/run-gpt-oss-vllm-amd-gpu-droplet-rocm" rel="noopener noreferrer"&gt;gpt-oss-120b&lt;/a&gt;, on two setups, self-hosted with vLLM on a single &lt;a href="https://www.digitalocean.com/blog/now-available-amd-instinct-mi300x-gpus" rel="noopener noreferrer"&gt;AMD MI300X GPU Droplet&lt;/a&gt;, and on &lt;a href="https://docs.digitalocean.com/products/inference/how-to/si-overview/" rel="noopener noreferrer"&gt;DigitalOcean's Serverless Inference&lt;/a&gt;. Then I measured the cold start, the warm latency, and the cost, and worked out exactly where one becomes the better choice than the other.&lt;/p&gt;

&lt;p&gt;In short, self-hosted GPU is faster and more consistent once it's warm, but it carries a real cold start and you pay for it around the clock. Serverless hides the cold start and costs almost nothing at low volume, but you pay per token. Which one wins comes down to your traffic shape and how much your model actually outputs. Here are the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How long is the cold start on a self-hosted GPU?
&lt;/h2&gt;

&lt;p&gt;When you run a model yourself, the GPU doesn't hold the model permanently. The weights have to be loaded from disk into the GPU's memory and the inference engine has to initialize before it can answer a single request. That startup delay, the gap between "process launched" and "first token out," is the cold start. You pay it every time you start the server fresh, a new deploy, a restart after a crash, or a new node coming up to handle load.&lt;/p&gt;

&lt;p&gt;My setup here was a single AMD MI300X GPU Droplet running gpt-oss-120b with vLLM, with the weights already cached on disk. I started vLLM from cold and timed how long it took before the first token came back.&lt;/p&gt;

&lt;p&gt;It took about &lt;strong&gt;61 seconds&lt;/strong&gt;. That wasn't a one-off, either, across three restarts it landed between 60.8 and 61.4 seconds every time.&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%2Fjnm67pdeefgjbtehcise.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%2Fjnm67pdeefgjbtehcise.png" alt="Cold Start of Model" width="762" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing worth being precise about, because it's the most common misread, that 61 seconds is &lt;em&gt;not&lt;/em&gt; the time to download the model. The weights were already saved on disk, so this is the cost you pay on a restart or redeploy, not a one-time setup. The startup logs show where the time actually goes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load weights from disk into VRAM (~68 GB)&lt;/td&gt;
&lt;td&gt;~24 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;torch.compile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CUDA graph capture&lt;/td&gt;
&lt;td&gt;~11 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engine init, KV cache, warmup&lt;/td&gt;
&lt;td&gt;~21 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the 61 seconds &lt;em&gt;includes&lt;/em&gt; compilation and warmup, the entire engine bring-up, right up to serving a request. What it &lt;em&gt;excludes&lt;/em&gt; is the one-time download of the weights from Hugging Face, which you pay once and never again. (These phases come from one representative run and there's some overhead between stages, so they don't sum to exactly 61, but that's where the time lives.)&lt;/p&gt;

&lt;p&gt;This also answers the obvious follow-up, what happens when you scale up? If a new replica boots from an image with the weights baked in, or mounts a shared volume that already has them, it pays this ~61-second load, not a download. A brand-new node with nothing staged would also have to pull the weights first, but well-run setups specifically avoid that, because you don't want every scale event re-downloading 68 GB. So 61 seconds is the realistic number for a properly configured restart or scale-up&lt;/p&gt;

&lt;h2&gt;
  
  
  Warm latency and throughput
&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%2Fp5isejarin6bm9q0aass.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%2Fp5isejarin6bm9q0aass.png" alt="Concurrent Benchmarking Of Warm Model" width="645" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the model is loaded, it's a different machine. Warm, the self-hosted MI300X returned the first token in about &lt;strong&gt;322 ms&lt;/strong&gt; and sustained roughly &lt;strong&gt;154 tokens per second&lt;/strong&gt;, and it was remarkably stable, across twenty requests, the spread was about two milliseconds.&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%2Fkryf08ri5xm8qlnm8vr5.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%2Fkryf08ri5xm8qlnm8vr5.png" alt="Benchmarking showcasing VRPAM used" width="800" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Memory is worth a note, because the raw number looks alarming. The card showed about &lt;strong&gt;173 GB of VRAM in use&lt;/strong&gt;. But the weights themselves are only about 68 GB. vLLM reserves most of the rest up front as KV-cache headroom (roughly 100 GB of it) so it can serve many requests at once. A 120B model doesn't "need" 173 GB; the engine just claims the room ahead of time.&lt;/p&gt;

&lt;p&gt;So the self-hosted trade-off is clean: once it's warm, it's fast, consistent, and entirely yours, but every cold start costs a full minute for our model, and you pay for the &lt;a href="https://www.digitalocean.com/pricing/gpu-droplets" rel="noopener noreferrer"&gt;GPU&lt;/a&gt; whether or not anyone is using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does serverless inference have a cold start?
&lt;/h2&gt;

&lt;p&gt;Next I ran the identical test against Serverless Inference. Calling it is straightforward, you create a &lt;a href="https://www.digitalocean.com/community/tutorials/serverless-inference-gradient" rel="noopener noreferrer"&gt;model access key&lt;/a&gt; and hit the &lt;a href="https://www.digitalocean.com/community/tutorials/serverless-inference-openai-sdk" rel="noopener noreferrer"&gt;OpenAI-compatible endpoint&lt;/a&gt;, so the client code is the same and only the base URL changes. I left the endpoint idle first, then measured first-token latency the same way.&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%2Fqjp5ov87pdm3dyvjqx38.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%2Fqjp5ov87pdm3dyvjqx38.png" alt="Concurrent Benchmarking Of Serverless" width="567" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first token came back in about &lt;strong&gt;546 ms&lt;/strong&gt;, with a wider spread, anywhere from 446 ms to roughly 1.3 seconds across twenty runs. But there was no spin-up. I ran it twenty times after sitting idle and never caught a cold-start hit.&lt;/p&gt;

&lt;p&gt;Two honest caveats on those numbers. First, the serverless requests travel over the network to DigitalOcean's endpoint, while the GPU test ran locally on the droplet, so some of that extra latency is network distance, not the model being slower. Here's the warm comparison side by side:&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;Self-hosted MI300X&lt;/th&gt;
&lt;th&gt;Serverless Inference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Median time to first token&lt;/td&gt;
&lt;td&gt;~322 ms&lt;/td&gt;
&lt;td&gt;~546 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spread (20 runs)&lt;/td&gt;
&lt;td&gt;~2 ms&lt;/td&gt;
&lt;td&gt;446 ms – 1.3 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;~154 tok/s&lt;/td&gt;
&lt;td&gt;(per-token billed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start&lt;/td&gt;
&lt;td&gt;~61 s&lt;/td&gt;
&lt;td&gt;none observed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So why no cold start on serverless? It didn't delete the cold start, it absorbed it. DigitalOcean &lt;a href="https://www.digitalocean.com/blog/serverless-inference-deep-dive" rel="noopener noreferrer"&gt;pools GPU capacity across customers&lt;/a&gt;, so the model stayed warm without any effort from me, and I never paid the 61-second hit I took on my own box. The difference is the billing model: serverless isn't charged by the hour, it's charged per token.&lt;/p&gt;

&lt;p&gt;To be fair, serverless &lt;a href="https://www.digitalocean.com/resources/articles/serverless-inference" rel="noopener noreferrer"&gt;isn't immune to cold starts&lt;/a&gt;. If you hit it during a genuinely quiet stretch, you can still catch one. The standard mitigations are sending periodic warm-up requests to keep a worker hot, or designing async-first so a slow first response doesn't matter. In this test I didn't need any of that, it just stayed warm.&lt;/p&gt;

&lt;h2&gt;
  
  
  When is serverless inference cheaper than your own GPU?
&lt;/h2&gt;

&lt;p&gt;This is where the decision actually lives, and it comes down to arithmetic. The GPU is a flat cost, about &lt;a href="https://www.digitalocean.com/pricing/gpu-droplets" rel="noopener noreferrer"&gt;$1.88 an hour&lt;/a&gt; for a single on-demand MI300X, the same whether it serves one request or a million. Serverless is usage-based, gpt-oss-120b is priced at $0.10 per million input tokens and $0.70 per million output tokens, so it costs almost nothing when you're quiet and climbs as you get busier.&lt;/p&gt;

&lt;p&gt;The break-even point is your hourly GPU cost divided by your per-request cost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;break-even requests/hour = GPU $/hr ÷ [(input_tokens × $0.10/1M) + (output_tokens × $0.70/1M)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The catch is that the per-request cost depends entirely on how much your model outputs, and that moves the crossover more than you'd expect. I measured it at three response lengths, on the same GPU at the same prices, changing only the output length:&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%2Fn2plb496wmer5azexrjs.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%2Fn2plb496wmer5azexrjs.png" alt="Benchmarking" width="586" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Response type&lt;/th&gt;
&lt;th&gt;Output tokens&lt;/th&gt;
&lt;th&gt;Crossover&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Short (classification / extraction)&lt;/td&gt;
&lt;td&gt;~30&lt;/td&gt;
&lt;td&gt;~18 requests/sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium (paragraph answer)&lt;/td&gt;
&lt;td&gt;~220&lt;/td&gt;
&lt;td&gt;~3 requests/sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long (code / detailed explanation)&lt;/td&gt;
&lt;td&gt;~1,200&lt;/td&gt;
&lt;td&gt;&amp;lt;1 request/sec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's roughly a 25x swing from identical hardware, with nothing changing but response length. Output tokens are the expensive side of the bill, so the chattier your app, the sooner owning a GPU pays for itself.&lt;/p&gt;

&lt;p&gt;In plain terms, if your app sends short, snappy responses, serverless stays cheaper until you're well over a dozen requests per second, nonstop. If it writes long answers, the GPU starts winning below one request per second. (For comparison, &lt;a href="https://docs.digitalocean.com/products/inference/details/pricing/" rel="noopener noreferrer"&gt;Dedicated Inference&lt;/a&gt;, DigitalOcean's managed always-on endpoint, is billed by the GPU-hour like the Droplet but without you managing the it, so its economics sit closer to the self-hosted side of this table than the serverless side.) Drop your own response lengths and GPU rate into the formula and you'll find your exact line.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use serverless inference, and when not to
&lt;/h2&gt;

&lt;p&gt;No "it depends." Here's the actual call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For most teams, serverless is the right default.&lt;/strong&gt; Bursty or spiky traffic, real idle stretches, a dev tool, an internal feature, a side project, anything async where nobody is staring at a spinner on the first request. In all of those, the cold start runs on someone else's pooled capacity, not yours, and you pay nothing while you're quiet. For that kind of traffic, it's almost perfect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run the GPU yourself when traffic is steady and high-volume, or when you have a latency SLA you can't miss.&lt;/strong&gt; At that point your traffic rarely stops, so you're not benefiting from serverless's idle savings anyway, and you're using the GPU enough that flat hourly beats per-token. You keep it warm, so the cold start stops mattering. That's not a knock on serverless, it's just the wrong tool for that job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're in between, start on serverless.&lt;/strong&gt; Watch your token spend, and move to a dedicated GPU the day you cross the line for your response lengths. Don't buy a GPU to solve a problem you don't have yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;Everything here is reproducible. You can spin up an &lt;a href="https://www.digitalocean.com/community/tutorials/run-gpt-oss-vllm-amd-gpu-droplet-rocm" rel="noopener noreferrer"&gt;AMD GPU Droplet and run gpt-oss-120b on vLLM&lt;/a&gt;, hit the same model on &lt;a href="https://docs.digitalocean.com/products/inference/how-to/si-overview/" rel="noopener noreferrer"&gt;Serverless Inference&lt;/a&gt; with a &lt;a href="https://www.digitalocean.com/community/tutorials/serverless-inference-openai-sdk" rel="noopener noreferrer"&gt;model access key&lt;/a&gt;, and check the &lt;a href="https://docs.digitalocean.com/products/inference/reference/serverless-inference-metrics/" rel="noopener noreferrer"&gt;serverless metrics&lt;/a&gt; and &lt;a href="https://docs.digitalocean.com/products/inference/details/pricing/" rel="noopener noreferrer"&gt;pricing pages&lt;/a&gt; against your own workload.&lt;/p&gt;

&lt;p&gt;Don't take my crossover, run your own. And if you measure a cold start on your own setup, I'd genuinely like to see how the spread looks across different models and hardware.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>We Got 2x LLM Inference Speed With Three Kubernetes Settings</title>
      <dc:creator>Yash Sharma</dc:creator>
      <pubDate>Tue, 19 May 2026 09:48:44 +0000</pubDate>
      <link>https://dev.to/yashsharam_f/we-got-2x-llm-inference-speed-with-three-kubernetes-settings-3l3n</link>
      <guid>https://dev.to/yashsharam_f/we-got-2x-llm-inference-speed-with-three-kubernetes-settings-3l3n</guid>
      <description>&lt;p&gt;Serving LLMs is not easy, especially when it comes to scalability, we have to optimise the infra to make sure we're actually able to serve inference to our customers.&lt;/p&gt;

&lt;p&gt;And when you start scaling LLM inference on Kubernetes, two problems quietly show up and cost you real money. The first one is where you put the model weights, because these files are huge, and every pod needs them. &lt;/p&gt;

&lt;p&gt;The second one is how fast your nodes can actually read those weights off shared storage, because if the network isn't tuned right, you leave a lot of throughput on the table.&lt;/p&gt;

&lt;p&gt;In today's video, I'll walk you through how we solved both at DigitalOcean using a reference architecture we built, vLLM on DOKS, with Managed NFS for shared model storage. The whole thing is open source, Terraform, Kubernetes manifests, everything. Link in the description.&lt;/p&gt;

&lt;p&gt;Let's go.&lt;/p&gt;

&lt;h2&gt;
  
  
  SECTION 1: ARCHITECTURE OVERVIEW
&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.amazonaws.com%2Fuploads%2Farticles%2F1vftt2u1p23wzt9uqto7.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.amazonaws.com%2Fuploads%2Farticles%2F1vftt2u1p23wzt9uqto7.png" alt=" " width="800" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me start with the full picture, so the rest of the video makes sense.&lt;/p&gt;

&lt;p&gt;Everything lives inside one VPC. Inside that VPC, there's a DOKS cluster with two node pools. The first is a management pool of regular droplets, these run system services and the model download job. The second is a GPU pool of H100 droplets, this is where vLLM actually runs.&lt;/p&gt;

&lt;p&gt;Next to the cluster, there's a Managed NFS share. The download job writes the model weights to it once. Every vLLM pod mounts it and reads from it. That's the whole storage story in one sentence.&lt;/p&gt;

&lt;p&gt;The whole thing is deployed as two Terraform stacks. Stack one builds the infrastructure VPC, cluster, NFS. Stack two deploys everything inside Kubernetes, the namespace, the persistent volume, the download job, vLLM, the gateway. They're split on purpose, so you can redeploy vLLM, swap models, or change replica counts without touching the underlying infrastructure.&lt;/p&gt;

&lt;p&gt;Now let me explain the two decisions that matter most, why NFS, and the network tuning piece.&lt;/p&gt;

&lt;h2&gt;
  
  
  SECTION 2: WHY NFS FOR MODEL STORAGE
&lt;/h2&gt;

&lt;p&gt;When you scale LLM inference, every pod needs the model weights. And these files are huge a 70B model is around 140 gigs.&lt;/p&gt;

&lt;p&gt;There are usually three approaches people try.&lt;/p&gt;

&lt;p&gt;The first is to download the weights from object storage on pod startup. So every pod, every restart, pulls the model from something like S3 or Spaces. For a 140-gig model, that's 15-20 mins on a cold node. Every autoscale event, you pay that cost again.&lt;/p&gt;

&lt;p&gt;The second is to bake the weights into the container image. Now your image is 140 gigs. Slow to pull, painful to update, and switching models means rebuilding the image every time.&lt;/p&gt;

&lt;p&gt;The third is block storage. This works fine for one pod, but block volumes are ReadWriteOnce — the moment you want multiple replicas, you're stuck.&lt;/p&gt;

&lt;p&gt;What we actually want is "download once, use many" one copy of the weights, and every pod reads from it. That's exactly what NFS gives us. Specifically, DigitalOcean Managed NFS, which supports ReadWriteMany and is available in the same regions as our H100 droplets NYC2 and ATL1 right now.&lt;/p&gt;

&lt;p&gt;The flow is simple. A Kubernetes Job runs once, pulls the model from HuggingFace onto the NFS share. Every vLLM pod mounts that share and reads from it. Scaling from one replica to three takes 20 to 30 seconds, because the weights are already there no re-downloading, no waiting.&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.amazonaws.com%2Fuploads%2Farticles%2Fai62cjdgwclaau3gmewn.gif" 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.amazonaws.com%2Fuploads%2Farticles%2Fai62cjdgwclaau3gmewn.gif" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A quick note on vLLM itself, since it's the serving layer. vLLM is an open-source inference engine, and it exposes an OpenAI-compatible API same&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/v1/chat/completions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;endpoint you're used to. For our architecture, vLLM just needs a folder with model files in it. It doesn't care that the folder is actually an NFS mount. That's why this setup works so cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  SECTION 3: THE NETWORK TUNER
&lt;/h2&gt;

&lt;p&gt;Okay, now the part that quietly matters the most&lt;/p&gt;

&lt;p&gt;First let’s understand what MTU mean, it is called Maximum Transmission Unit (MTU) which defines the maximum size of a network packet that can be transmitted over a network interface without fragmentation.&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.amazonaws.com%2Fuploads%2Farticles%2Fg02f0nc3vkiz1wo39vkq.gif" 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.amazonaws.com%2Fuploads%2Farticles%2Fg02f0nc3vkiz1wo39vkq.gif" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, DOKS nodes use a 1500-byte MTU and pretty small TCP buffer sizes. For most workloads, that's totally fine. But for NFS reads of multi-gigabyte weight files, it leaves a lot of throughput on the table and we want to ensure to utilize it properly.&lt;/p&gt;

&lt;p&gt;We benchmarked it. With default settings, we got around 420 MB/s loading model weights from NFS. With tuning applied, around 880 MB/s. That's roughly 2x faster — same hardware, same NFS share, same model.&lt;/p&gt;

&lt;p&gt;Now let’s see Three changes get us there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first is jumbo frames&lt;/strong&gt;. We bump the MTU on the private network from 1500 bytes up to 9000. When vLLM pods read file from NFS packet by packet for 140gigabyte model, its 93 million packets however when we make MTU to 9000 it’s 15 million.&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.amazonaws.com%2Fuploads%2Farticles%2Fzl693pbny1vsxn7m67zo.gif" 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.amazonaws.com%2Fuploads%2Farticles%2Fzl693pbny1vsxn7m67zo.gif" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bigger packets means less overhead per packet during large transfers. One thing to note here — this only works on GPU droplets. Standard droplets don't support jumbo frames on the private network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second is bigger TCP buffers.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rmem_max, wmem_max, tcp_rmem, tcp_wmem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We raise all of these to 16 megabytes. This lets the kernel actually use the bandwidth that's available during high-throughput NFS reads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The third is on the NFS mount itself&lt;/strong&gt; — nconnect=8. Instead of opening one TCP connection per mount, each pod opens eight. More connections, more aggregate throughput.&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.amazonaws.com%2Fuploads%2Farticles%2Fwu7mubn8soqi3fy14yqo.gif" 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.amazonaws.com%2Fuploads%2Farticles%2Fwu7mubn8soqi3fy14yqo.gif" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And all of these tuning is done by a demonset on when a node freshly joins the cluster&lt;/p&gt;

&lt;p&gt;Now here's the tricky part, and this is what I was hinting at in the intro.&lt;/p&gt;

&lt;p&gt;When a fresh GPU node joins the cluster, two things want to run on it right away, the network tuner, and the vLLM pod. If the vLLM pod wins that race and mounts NFS first, TCP negotiates the packet size based on the old 1500-byte MTU. And here's the thing, that number never changes after the handshake. So even if the tuner runs five seconds later and raises the MTU to 9000, that specific NFS mount is locked at degraded speed for its entire lifetime. The only way to fix it is to remount.&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.amazonaws.com%2Fuploads%2Farticles%2F8vihszsjljqkijlbk2eq.gif" 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.amazonaws.com%2Fuploads%2Farticles%2F8vihszsjljqkijlbk2eq.gif" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The fix is a node taint.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When a GPU node joins the cluster, it comes up with a taint called network-not-tuned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node.digitalocean.com/network-not-tuned:NoSchedule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That taint blocks every workload pod from being scheduled on the node. But the network tuner DaemonSet is built to tolerate it, so it schedules right away. It tunes the network sets jumbo frames, raises the TCP buffers and then removes the taint. Only after that do vLLM pods get to schedule.&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.amazonaws.com%2Fuploads%2Farticles%2Fmkhy3e7fjzs4v711exy5.gif" 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.amazonaws.com%2Fuploads%2Farticles%2Fmkhy3e7fjzs4v711exy5.gif" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the guarantee is simple if a vLLM pod is running on a node, the network on that node was already tuned before the pod ever touched NFS.&lt;/p&gt;

&lt;p&gt;This matters most with autoscaling. Every time the cluster brings up a new GPU node, it goes through this exact sequence. This helps us avoid the race conditions&lt;/p&gt;

&lt;h2&gt;
  
  
  SECTION 4: HA CHOICES — AND WHY
&lt;/h2&gt;

&lt;p&gt;Before the demo, a few production choices worth explaining quickly, because the reasoning matters more than the config itself.&lt;/p&gt;

&lt;p&gt;For rolling updates, we use maxSurge: 0, maxUnavailable: 1. The default behaviour is to spin up an extra pod during a rollout but on GPU-constrained clusters, you often don't have a spare H100 sitting around. So we'd rather accept one moment of reduced capacity than block the rollout waiting for a GPU that isn't coming.&lt;/p&gt;

&lt;p&gt;The startup probe is set to 120 seconds, because loading a 70B model from NFS into VRAM takes real time  220sec in our case. Default health probes would kill the pod before it ever finished loading.&lt;/p&gt;

&lt;p&gt;There's also a PreStop hook that drains in-flight requests before the pod terminates. vLLM batches requests on the GPU, so if you just send SIGTERM, those requests get dropped. The hook polls vLLM's metrics endpoint, waits until the queue is empty, then shuts down cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  SECTION 5: DEMO
&lt;/h2&gt;

&lt;p&gt;For quick demo checkout the video at &lt;/p&gt;

&lt;h2&gt;
  
  
  SECTION 6: WRAP
&lt;/h2&gt;

&lt;p&gt;That's the architecture. Managed NFS for shared weights, a taint-plus-DaemonSet pattern to make sure the network is tuned before any NFS mount happens, and a few HA choices that make sense specifically for GPU-constrained clusters.&lt;/p&gt;

&lt;p&gt;The full reference architecture is open source in the &lt;a href="https://github.com/digitalocean/scale-with-simplicity/tree/main/reference-architectures/vllm-nfs" rel="noopener noreferrer"&gt;scale-with-simplicity repo&lt;/a&gt;. See you in the next one.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
