Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

AI System Design

Deploy an On-Prem LLM

Step 1 / 9

Learn AI system design by deploying a large language model inside a customer’s own environment — the constraint a forward deployed engineer meets when a regulated customer won’t send data to a…

The numbers to beatfixed VRAMnot elasticINT4 / INT8quantize to fitvalidateon their box

The whole design, in writing

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

Why deploy in the customer’s walls?

A regulated customer — a bank, a hospital, a defense contractor — wants your AI product, but their data cannot leave their environment to hit a public API. Compliance, contracts, and sometimes law forbid it. A hosted endpoint, however good, is a non-starter for them.

Bring the model to the data: deploy the LLM inside the customer’s own environment (their VPC, their data center, or fully air-gapped). Right-size it to their hardware, serve it efficiently, run it with no network egress, and patch and observe it without anything leaving. It’s the deployment shape that turns a hard "no" into a signed deal.

Step 1 · The skeleton

Run the model where the data is

The naive plan is your cloud endpoint — but the customer’s data can’t reach it. So the request path itself has to move: their apps must call a model that lives inside their network, not across the internet.

Customer AppsInference GatewayInference ServerModel Weights
New in this step: Customer Apps, Inference Gateway, Inference Server, Model Weights. · swipe to pan the diagram

Stand up an Inference Gateway and Inference Server inside the customer’s environment, loading local Model Weights. To the customer’s apps it looks like the same API — but every byte of prompt and response stays in their walls.

Why this piece earns its place

The gateway is a seam you will never be on the other side of — and neither is anything else here, because the whole deployment sits behind walls you have no account on. That changes what debuggable means. A fault reaches you second-hand, described by someone reading a screen you cannot see, so the gateway has to answer the first diagnostic questions in one place and without a debugger: a health endpoint that prints the loaded model, its precision, the VRAM headroom and the bundle version, in output an operator can paste into a ticket. Anything that needs a flag only you know how to set is unreachable in practice. The gateway is also where identity stops being yours to design. There is no account system to build — it delegates to whatever provider they already run, and writes who prompted what to a local audit log, which a regulated buyer asks to see during procurement rather than after go-live. And the seam is what lets there be a second deployment at all. Each site pins its own version on its own schedule, so there is no single current release; the request and response shape at this boundary is the contract you have to keep compatible with the oldest instance still in the field.

What the new pieces do

Customer Appsclient
The customer’s own users and applications, calling the model from inside their network. They never talk to a public API — the whole point is that the request and the data never leave their environment.
Inference Gatewaybackend
The API surface that lives inside the customer’s environment. It looks like the cloud endpoint your app already calls, so integration is a config change — but every byte stays local.
Inference Servercache
The serving runtime (vLLM, TGI, llama.cpp) that loads the weights and answers requests. Continuous batching and a KV cache keep the limited GPUs busy so throughput is usable on modest hardware.
Model Weightsdata
The model files, stored on the customer’s own disk and loaded by the server. They never come from a live download at request time — they’re part of the signed bundle that was imported.

Step 2 · Make it fit

Right-size to their hardware

In the cloud you pick the GPU. On-prem, the customer already owns fixed, often modest hardware — and you can’t add more on demand. Ship the biggest full-precision model and it won’t load, or OOMs on the first long prompt.

Right-Sizingmodel + quantizationInference ServervLLM / TGIModel Weightsstored locally
New in this step: Right-Sizing.

The customer has two mid-range GPUs with fixed VRAM. How do you choose what to deploy?

  1. The largest model in full precision won’t fit fixed, modest VRAM — it fails to load or OOMs on a long prompt. On-prem you can’t just provision a bigger GPU, so the biggest model is the wrong default: it has to fit the hardware first.

  2. Sometimes true, but it’s slow, expensive, and often not an option in a locked-down data center. A good FDE first right-sizes to what they have — model choice plus quantization — and only escalates hardware if the requirement genuinely can’t be met.

  3. Right-sizing is the on-prem skill: choose a model whose weights, quantized as needed, fit their VRAM with room for the KV cache, and validate latency on their actual hardware. Fit first, then maximize quality within that envelope.

Add a Right-Sizing step: pick the model and precision that fit the customer’s VRAM — a smaller and/or quantized (INT8/INT4) model — leaving headroom for the KV cache, and validate latency on their hardware before you commit.

Why this piece earns its place

Quantization is where this step is won, and the bill arrives somewhere you cannot look. Fit is settled in seconds — weights load or they do not. Quality loss is not evenly spread: it concentrates on code, on long-context reasoning, on languages and formats that were thin in whatever calibration set produced the quantized weights, which is precisely the tail a demo never reaches. So the comparison has to be scoped to what can actually run in the room. The full-precision arm the arithmetic above already ruled out cannot be the baseline; the candidates are the ones that fit — this model at INT8 against it at INT4, or a smaller model at higher precision against a larger one at lower. Their prompts cannot come to you, so their people run the comparison and their people score it, which means what you are really writing is a rubric and a harness, not a result. INT4 is also not one format, and the kernels that make a given scheme fast exist per GPU generation and driver version, so a mixed fleet can leave you serving different precisions on different boxes in the same rack. Leave headroom now, while headroom is free: acceptance is signed against one model at one precision, and changing it afterwards reopens that whole cycle.

  • fixed VRAMnot elastic
  • INT4 / INT8quantize to fit
  • validateon their box

What the new pieces do

Right-Sizingservice
Chooses the model and precision that fit the hardware the customer actually has — a smaller model and/or quantized weights (INT8/INT4) so it loads in their VRAM and hits an acceptable latency.

Back of the envelope

70B × 2 bytes/param = 140 GB of weights at FP16
more than any single card holds — this is the “won’t load” case
INT4 is ½ byte/param ⇒ 70B × 0.5 = 35 GB
same model, a quarter of the VRAM — the trade is quality, not capability
KV cache ≈ 0.33 MB/token (2 × 80 layers × 8 KV heads × 128 dim × 2 bytes) ⇒ ≈2.7 GB per 8K request
the weights load fine and it still OOMs on the first long prompt — this is why
80 GB card − 35 GB weights = 45 GB ⇒ 45 ÷ 2.7 ≈ 16 concurrent 8K requests
the batch size is not a guess — it falls out of the arithmetic, and Step 3 tunes it
then benchmark on their hardware
the numbers above size the box; they don’t predict its throughput

Step 3 · Keep the GPUs busy

Serve efficiently on limited GPUs

A right-sized model that answers one request at a time wastes the very GPUs you fought to fit it on. On modest hardware you can’t brute-force throughput with more machines — you have to use what’s there well.

Inference ServervLLM / TGIModel Weightsstored locallyGPU Poolfixed VRAM
New in this step: GPU Pool.

Serve through a runtime built for it: continuous batching packs concurrent requests through the GPU Pool, and a KV cache (with paged attention) makes each token cheap. You get usable throughput and latency from fixed hardware instead of one-at-a-time serving.

Why this piece earns its place

Continuous batching does not create capacity. It converts a capacity problem into a queueing problem, and queueing is the part that shows up in production: mean throughput improves while the tail gets worse, and there is no autoscaler here to absorb a burst. The pool is the pool. So the first thing this step owes the design is admission control — a queue-depth ceiling that refuses fast and says so, because a caller left hanging is worse for the customer app than an explicit rejection it can retry. The second is less obvious. When the KV cache fills, the scheduler does not politely stop admitting; it preempts, evicting a running sequence and recomputing its tokens later, so a burst costs you work you had already paid for and a saturated pool degrades faster than a utilization graph suggests. And because this is one shared pool inside one organisation, the workloads collide: an interactive caller wants a short time to first token, a nightly job wants tokens per second, and batching them together makes both worse than either alone. Separate queues, or separate hours. The compensation is that you know the organisation — its working day, its quiet night — which no multi-tenant service does, so the heavy work can be scheduled rather than bought.

  • continuousbatching
  • KV cachecheap tokens
  • fixed poolwell used

What the new pieces do

GPU Pooldata
The physical accelerators the customer owns — a fixed, often modest amount of VRAM you cannot expand on demand. Serving choices (batch size, context length, quantization) are all bounded by this budget.

Back of the envelope

continuous batching
many requests, full GPU
paged KV cache
long contexts without waste
tune batch + context
to the VRAM budget

Step 4 · Nothing leaves

No egress / air-gap

Everything so far runs locally — but a single outbound call breaks the promise. A telemetry beacon, an online license check, or a model tool that fetches a URL can carry prompts and outputs out of the customer’s environment. The requirement isn’t "encrypted egress" — it’s no egress.

Right-Sizingmodel + quantizationInference ServervLLM / TGINo-Egress Boundaryair-gap / deny-outModel Weightsstored locally
New in this step: No-Egress Boundary.

The customer requires that data never leave their network. What must the deployment guarantee?

  1. The guarantee is zero egress: block outbound traffic at the boundary, make licensing and config fully offline, and disable any model tool that can fetch a URL. If nothing can call out, nothing can leak — which is the exact requirement that justified running on-prem.

  2. Encryption protects data in transit, but the requirement is that the data doesn’t leave at all. An encrypted beacon still exfiltrates prompts/outputs to a third party — a compliance failure regardless of TLS. No egress means no outbound call, period.

  3. "Anonymized" still means data crosses the boundary, and prompts are notoriously hard to truly anonymize. For a customer whose whole reason for going on-prem is that nothing leaves, any outbound flow — even aggregated — breaks the deal. Keep telemetry local (Step 6).

Enforce a No-Egress Boundary: deny outbound traffic, make licensing and configuration fully offline, and disable any tool or feature that can fetch a URL. Verify it — a locked-down customer will test that the box truly can’t call home.

Why this piece earns its place

Almost nobody writes the outbound call themselves. A dependency does, on import, by default — a crash reporter that ships enabled, a TLS stack fetching a revocation list, a package index refresh, a container runtime pulling a layer it does not have locally. So the work is an inventory of what every transitive dependency does to the network, and verification only means anything if it is continuous: the whole stack brought up with the interface down, in CI, on every build, rather than once at installation on a machine that still had a route out. The detail that bites afterwards is the shape of the failure. A default-deny policy drops packets rather than rejecting them, so an attempted phone-home never raises an error anyone would grep for — it surfaces as a request that hangs until something times out, often a name lookup first, on a code path with no obvious relationship to the call. Set short connect timeouts everywhere, and ask for denied flows to be logged at the boundary, because their firewall log is the only place the truth gets written and you will be reading it over someone’s shoulder. Test a cold restart too, not just a running box: a deployment that has been up for months can fail to come back because something it needs at start has been quietly resolving a hostname since install day.

  • denyoutbound
  • offlinelicensing
  • noURL fetches

What the new pieces do

No-Egress Boundaryservice
Enforces that nothing calls out: no telemetry beacon, no online license check, no model tool that fetches a URL. Licensing and config are offline. Blocking outbound traffic is the requirement that justifies the whole deployment.

Back of the envelope

block outbound at the boundary
default-deny egress
offline licensing + config
no phone-home
disable URL-fetching tools
no covert exfil path

Step 5 · Ship fixes without internet

Updates in a locked-down env

A model with no egress also can’t auto-update. But it still needs new model versions and security patches — and CVEs don’t wait. You need a way to get changes in that respects the same no-egress rule you just enforced.

Customer OpsOffline Updates
New in this step: Customer Ops, Offline Updates. · swipe to pan the diagram

Distribute Offline Updates as signed, verifiable bundles the customer’s Ops team imports on their schedule — weights, runtime, and patches together — with a clear rollback. Signing lets them trust the artifact without trusting a network connection to you.

Why this piece earns its place

Signing settles authenticity and creates a key problem. With no network there is no revocation check, so a compromised signing key cannot be withdrawn from a deployed instance — rotation has to travel the way everything else does, with bundle N carrying the key that will verify bundle N+1, and the first trust root established by hand at install. Rolling back is the other thing that is not what it looks like. Reverting a binary is not reverting a deployment: if the new runtime migrated anything on disk — a cache format, an index layout, a config schema — the old version comes back up reading files it does not understand, and they discover that mid-window, under their own change control, with you unreachable. Either ship migrations that reverse, or keep the previous version able to read the new format. Underneath both sits a constraint worth naming out loud: their maintenance window sets your CVE response time, not your release cadence. You cannot patch quickly, so the real defence is having less to patch — a minimal image, fewer packages, fewer lines in the scanner report you are contractually obliged to answer within a fixed number of days.

  • signedbundles
  • customerimports
  • cleanrollback

What the new pieces do

Customer Opsclient
The customer’s own operations and security team. After handoff they run the box: apply updates you ship, watch health, and hold the keys. Your deployment has to be operable by them, not only by you.
Offline Updatesservice
Ships new model versions and security patches into a locked-down environment without internet — signed, verifiable bundles the customer’s team imports and rolls back, instead of an auto-updater phoning home.

Back of the envelope

signed, verifiable artifacts
trust without a network
import on the customer’s schedule
their change control
versioned with rollback
a bad update reverts

Step 6 · See without exfiltrating

Local observability

You still need to know the deployment is healthy — latency, errors, GPU saturation — but you can’t stream metrics back to yourself without breaking no-egress. Flying blind isn’t acceptable either; a stuck GPU or a bad rollout has to be visible.

Customer OpsInference GatewayInference ServerOffline UpdatesLocal Telemetry
New in this step: Local Telemetry. · swipe to pan the diagram

Collect Local Telemetry that stays in the customer’s environment on a dashboard they own. When you need to debug, the customer exports and shares exactly what they choose. You get observability; they keep control of every byte that leaves.

Why this piece earns its place

The signal you most want is the one that can never leave. When an answer is wrong, the prompt and the output are what explain it, and those are the exact bytes this deployment exists to keep in place. So it only works if the telemetry is content-free by construction: shapes, not contents. Token counts, sequence lengths, finish reasons, queue depth, preemption counts, error codes, model and bundle version. Redacting after the fact is the thing that fails, because eventually someone pastes a stack trace carrying a prompt into a ticket. Design the exportable slice to be safe on its face, publish its schema, and have it approved once — then sharing is a file their team sends, not a fresh legal review each time. Alerting is the harder half, because it is not yours. Thresholds have to arrive in the bundle with defaults already set: nobody on their side knows what a bad p99 looks like for this model on this hardware, and the person who does know is not on the rotation. There is also no fleet to compare against — one site, one configuration, no other instance quietly telling you what normal is. The acceptance benchmark is the entire baseline, so if nobody wrote those numbers down on install day, the first incident has no before.

  • metricsstay local
  • theyown it
  • exporton request

What the new pieces do

Local Telemetryservice
Health, latency, and error metrics collected and stored inside the customer’s environment, on a dashboard they own. You get observability without any data leaving — the customer decides what, if anything, they share back.

Back of the envelope

local metrics + logs
health without egress
customer-owned dashboard
they hold the data
share by explicit export
never an auto-stream

You did it

You just deployed an LLM in a customer’s environment.

Customer AppsCustomer OpsInference GatewayRight-SizingInference ServerOffline UpdatesNo-Egress BoundaryModel WeightsGPU PoolLocal Telemetry
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • When data can’t leave, move the model to the data — deploy inside the customer’s walls.
  • Right-size the model and precision to the fixed hardware they actually own; fit first.
  • Serve with continuous batching + a KV cache to use limited GPUs well — utilization, not more boxes.
  • Enforce no egress: offline licensing, no beacons, no URL fetches — and prove it.
  • Ship updates as signed bundles the customer imports and can roll back — no auto-update.
  • Keep telemetry local on a dashboard they own; share only by explicit export.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why would a customer insist on on-prem instead of a hosted API?

    For regulated or sensitive-data customers, sending prompts and documents to a third-party API can violate compliance regimes (HIPAA, GDPR data-residency, financial regulations), contractual data-handling terms, or outright law — and for some (defense, critical infrastructure) the environment is air-gapped by policy. No amount of "we’re SOC 2 certified" changes that the data physically left their control. Deploying the model inside their environment removes the objection entirely: the data never crosses the boundary, so the compliance question mostly disappears. For an FDE, being able to offer this is often the difference between closing a large regulated account and losing it.

  2. Why is right-sizing the model the first on-prem concern?

    Because in the cloud you choose the hardware to fit the model, but on-prem the hardware is fixed and often modest, and you usually can’t expand it on demand. A large full-precision model simply won’t load in limited VRAM, or will OOM once the KV cache grows on a long prompt. So the first job is to fit: pick a model whose weights (quantized to INT8/INT4 if needed) sit inside the VRAM budget with headroom for the cache, then validate real latency on the customer’s actual GPUs. You maximize quality within that envelope rather than assuming you can have the biggest model.

  3. What does "no egress" really require, beyond a firewall rule?

    It requires that nothing in the deployment can make an outbound network call, and that you can prove it. That means offline licensing (no online activation or license heartbeat), no usage telemetry beaconing home, and disabling any model capability that fetches URLs (tools, retrieval from external sources, even auto-download of a tokenizer or update). Encryption doesn’t satisfy it — an encrypted beacon still exfiltrates. A default-deny egress policy at the boundary is the backstop, but the software itself has to be built to not need the network. And because a locked-down customer will actively test that the box can’t phone home, no-egress is something you verify and demonstrate, not just assert.

  4. How do you update a model that has no internet access?

    You distribute updates as signed, verifiable bundles that the customer’s own operations team imports on their schedule, rather than an auto-updater that pulls from your servers. The bundle packages the new weights, runtime, and any patches together; the signature lets the customer verify the artifact’s authenticity and integrity without needing a trusted live connection to you. Versioning with a clean rollback path is essential, because if a new model or runtime misbehaves in their environment, they need to revert quickly under their own change-control process. This keeps the no-egress guarantee intact — updates come in as artifacts, never as live downloads — while still letting you ship security fixes and model improvements.

  5. How do you get observability without breaking no-egress?

    You keep telemetry local: metrics, logs, and traces are collected and stored inside the customer’s environment, on dashboards the customer owns, rather than streamed back to you. You still get everything you need to know the deployment is healthy — latency, error rates, GPU saturation, queue depth — but the data physically stays put. When you need to debug something specific, the customer exports the relevant slice and shares it with you deliberately, so a human decides what leaves rather than an always-on pipe. This preserves the core promise (nothing leaves without consent) while avoiding the other failure mode, flying blind, which is just as unacceptable in production.

Check yourself — the answers, and why

Seven steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. A regulated customer insists their data never reach a public API. The right deployment is to…

    When data can’t leave, move the model to the data — run it in their VPC, data center, or air-gapped.

  2. On-prem, the customer’s GPUs have fixed VRAM. Your first job is to…

    Fit the fixed hardware first — model choice plus INT8/INT4 quantization — then maximize quality within it.

  3. "No egress" for an air-gapped deployment means…

    The data must not leave at all; any outbound flow — even encrypted or aggregated — breaks the promise.

  4. You ship a model update to a locked-down environment by…

    No egress means no live download — updates come in as signed artifacts under the customer’s change control.

  5. Observability for an on-prem deployment should…

    Keep telemetry in-environment so you can operate without exfiltrating; the customer decides what leaves.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Serve in-environment: the customer’s apps call an Inference Gateway inside their network — no byte leaves.
  • Right-size: pick a model and precision (quantization) that fit the VRAM they actually own.
  • Serve efficiently: continuous batching + a KV cache keep limited GPUs busy for usable throughput.
  • No egress: block all outbound calls — offline licensing, no telemetry beacons, no URL fetches.
  • Update + observe offline: import signed update bundles, and keep telemetry on a dashboard the customer owns.

The qualities that shape everything

Each one names the mechanism that buys it.

The customer’s data never leaves their walls
Move the model to the data — an Inference Gateway and server run inside their environment loading local weights; the endpoint just changes location.
The model actually loads on fixed hardware
Right-sizing — a smaller and/or quantized (INT8/INT4) model that fits VRAM with KV headroom, latency validated on their own box.
Usable throughput without adding machines
Continuous batching packs concurrent requests through the GPU pool and a paged KV cache makes each token cheap.
Nothing can call home
A no-egress boundary denies outbound traffic, licensing and config are offline, and any URL-fetching tool is disabled — and it’s verified.
Ship CVE fixes into a locked-down box
Offline updates as signed, verifiable bundles the customer imports on their schedule, with a clean rollback — no auto-updater.
Operate it without flying blind or exfiltrating
Local telemetry stays in-environment on a customer-owned dashboard; they export and share only what they choose.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Move the model to the data over a hosted cloud endpoint

A regulated customer’s data can’t reach a public API — compliance, contracts, sometimes law forbid it. Running the model inside their walls keeps every prompt and output local, turning a hard "no" into a deal.

Right-size to their hardware over deploying the largest, highest-quality model

The biggest full-precision model won’t fit fixed, modest VRAM — it fails to load or OOMs on a long prompt, and on-prem you can’t just provision a bigger GPU. Fit first (model + quantization), maximize quality inside that envelope.

Raise utilization over brute-forcing throughput with more machines

On modest, fixed hardware you can’t add boxes. Continuous batching and a paged KV cache serve many users at once from the same GPU pool — utilization is the only lever left.

Zero egress over encrypting outbound telemetry

Encryption protects data in transit, but the requirement is that data doesn’t leave at all. An encrypted beacon still exfiltrates prompts to a third party — a compliance failure regardless of TLS. No outbound call, period.

Signed import bundles over an auto-updater that phones home

No egress cuts both ways — you can’t push. Hand over signed, verifiable bundles the customer imports and can roll back; trust comes from the signature, not a live channel to you.

The answer, out loud

What a strong answer to “Deploy an LLM in a Customer’s Environment” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–3 min

    Start from the constraint, not the model

    I’d start by naming the constraint, because it decides everything after it. This is a regulated customer — a bank, a hospital, a defense contractor — and their data cannot leave their environment to reach a public API. That’s compliance, contracts, sometimes law, and no certification changes the fact that the bytes physically left their control. So I’m not designing a better hosted endpoint. I’m designing a deployment that runs inside their walls, on hardware they already own, with nothing calling out. Every choice I make from here is downstream of that one sentence.

  2. 3–8 min

    Move the model to the data

    The skeleton’s small and I’d draw it fast — their apps, a gateway inside their network, a server behind it, weights on their disk. Where I’d spend the time is why the obvious alternative fails, since that’s what the interviewer is really asking. Encrypting the traffic to my cloud sounds like it should satisfy a security team. It doesn’t, because the requirement isn’t confidentiality in transit, it’s jurisdiction — the bytes either crossed the boundary or they didn’t. Once you accept that, the shape is forced. I’d also flag here that this is something I hand over rather than something I run, because that distinction is what makes the last three steps hard.

    Built in step 1: Run the model where the data is
  3. 8–16 min

    Make it fit before anything else

    This is where I’d slow down, because it’s where an interviewer finds out whether I’ve done it. The model has to fit cards bought for something else, and there’s no escape hatch. I’d write the arithmetic out rather than describe it — weights at a chosen precision, then cache per token times context times the concurrency I’m promising. That second term is the one people drop, and it decides how many users this serves: weights that load with room to spare still fall over on the first long prompt. If I’m pushed on quantization I wouldn’t defend it as free. The loss is real, it sits in the tail, and I can’t say from here what it costs on their traffic — I can only set the comparison up so their people run it. Then I’d stop calculating and measure, and I’d call that benchmark a deliverable with a date, because acceptance gets written against it.

    Built in step 2: Right-size to their hardware
  4. 16–22 min

    Utilization is the only lever left

    With the fit settled, throughput. On-prem I can’t add machines, so the only lever is using the pool I have properly. I’d serve through a runtime built for that — vLLM or TGI — so concurrent requests are packed through the GPU with continuous batching instead of answered one at a time, and a paged KV cache keeps long contexts from wasting memory. Then I’d tune batch size and maximum context against the VRAM budget, which is the same budget the previous step’s arithmetic handed me. Utilization is what turns modest, fixed hardware into something several teams can share.

    Built in step 3: Serve efficiently on limited GPUs
  5. 22–28 min

    No egress, and I’d prove it

    Now the promise itself. I’d frame it as a property of the build rather than a firewall rule — that’s the distinction I want credit for. Deny-all at the boundary takes an afternoon. The hard part is that everything underneath was written assuming a network exists, and none of it announces that. So what I’d commit to is an inventory of what every dependency touches at start-up, shipped with the build — because what their security team wants is a list they can check, not an assurance from me. If the interviewer offers me the middle ground — encrypt it, aggregate it, anonymize it — I’d decline all three for one reason: those are arguments about what’s inside the packet, and the requirement is about whether a packet exists at all.

    Built in step 4: No egress / air-gap
  6. 28–34 min

    Getting fixes in without a network

    No egress cuts both ways, and people forget the direction. If nothing can call out, I can’t reach in either — no push, no remote fix, no quiet patch at midnight. Everything I want to change becomes an artifact somebody else chooses to install, so their change window is my release train: a bad CVE gets fixed when they open it, not when I ship it. I’d design for that rather than complain about it, and keep the image small so less can ever need an emergency. And signing isn’t ceremony here: with no network there’s nothing to ask whether a file is really mine, so the signature carries that on its own.

    Built in step 5: Updates in a locked-down env
  7. 34–40 min

    Seeing it without exfiltrating

    The last piece is the one I’d expect to get pushed on, because keeping metrics local can sound like a dodge. So I’d be concrete. The telemetry has to be built to be shareable — counters and durations, nothing with a prompt inside it — because the moment an export needs redacting it stops being routine and becomes a legal question, and then nobody exports anything and I’m debugging by anecdote. Get that right and a support request is a file somebody attaches; get it wrong and the design is compliant and unusable. The trade is that I never see the thing that would explain a bad answer, and I’d say so plainly rather than pretend counters are as good.

    Built in step 6: Local observability
  8. 40–45 min

    The trade-off, and what I’d do with more time

    To close: move the model to the data, fit it to hardware I don’t get to choose, buy throughput with utilization because I can’t buy it with machines, block egress and prove it, ship changes as artifacts they import, keep the watching local. The trade underneath all of it is that I give up elasticity and I give up the feedback loop — nothing ever comes back to me on its own — for a customer who otherwise couldn’t say yes. Given more time I’d work on what happens when they outgrow the pool: there’s no autoscaler, the answer is a purchase order with a lead time, and it has to be a conversation months before it’s an incident.

What this teaches

Learn AI system design by deploying a large language model inside a customer’s own environment — the constraint a forward deployed engineer meets when a regulated customer won’t send data to a public API. An interactive guide covering right-sizing the model to the hardware they actually have, serving it efficiently on limited GPUs, running with no network egress (air-gapped, no phone-home), shipping model and security updates into a locked-down environment, and getting observability out without exfiltrating customer data.

Key takeaways

  • When data can’t leave, move the model to the data — deploy inside the customer’s walls.
  • Right-size the model and precision to the fixed hardware they actually own; fit first.
  • Serve with continuous batching + a KV cache to use limited GPUs well — utilization, not more boxes.
  • Enforce no egress: offline licensing, no beacons, no URL fetches — and prove it.
  • Ship updates as signed bundles the customer imports and can roll back — no auto-update.
  • Keep telemetry local on a dashboard they own; share only by explicit export.

Concepts covered

  • Why deploy in the customer’s walls?
  • Run the model where the data is
  • Right-size to their hardware
  • Serve efficiently on limited GPUs
  • No egress / air-gap
  • Updates in a locked-down env
  • Local observability
RUN IT YOURSELF

Sized on the mean, sunk by the spread

Step 2 sizes this pool at 16 concurrent slots; this file finds out what that actually serves. It queues 20,000 requests through those slots across five independent arrival streams and bisects for the exact load at which p95 wait breaks the SLO — for two workloads with the identical mean generation time, one steady, one mixing interactive prompts with a nightly batch job. Change LONG_SHARE, hit Run, and watch the printed cv and the capacity cost move together.

HOW TO READ THE CODE — 6 IDEAS
  1. The 16 slots are not invented: they are step 2’s own arithmetic, (80 GB card − 35 GB INT4 weights) ÷ 2.7 GB of KV cache per 8K request.
  2. Throughput rises linearly with utilization; wait does not. The steady column’s p95 goes 1.1s → 6.8s → 13.2s for 70% → 90% → 95% load — the last five points cost more than the first seventy.
  3. Both columns carry the same mean generation time and are read off the same arrival draws — raising the load only speeds up the clock, it does not redraw who arrives. Only the spread differs, so nothing but variance can explain the gap.
  4. Continuous batching does not create capacity — it turns capacity into queueing (step 3). The knee falls from 0.909 to 0.772 utilization: 15% of the pool, on hardware you cannot add to.
  5. The knee is bisected, not a grid rung, and averaged over five streams, because a rung is a step function of a noisy number: the grid version of this file printed 17% or 12% depending on nothing but the seed.
  6. The cost tracks cv, not LONG_SHARE — and cv peaks near a 5% long-job share, then falls as the long job takes over. Push LONG_SHARE past 0.5 and the cost comes back down, because the mix is no longer mixed.
CPython · WebAssembly
built to run in the customer’s walls, not memorized — right-size, serve, no egress, patch offline, watch locally.
Finished this one? 0 / 65 AI System Designs done
NextDesign Secure Document Ingestion + RAG Next in “Become a Forward Deployed Engineer” · step 42 of 63

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More AI System Designs