Project Overview

Easy-vLLM answers the two questions everybody has before deploying a model with vLLM: will this fit on my GPU, and which flags do I actually need. You type a Hugging Face model id, pick your card, and get back a working vllm serve command, a docker-compose.yml that matches your hardware, and an honest verdict on whether the thing will start.

Easy-vLLM showing Qwen3-8B on an RTX 4090 at 140 percent of VRAM, marked will not fit

It runs entirely in the browser. No install, no account, no server — the only network request it makes is to huggingface.co for the public config.json of the model you named. It is Apache-2.0 and hosted on GitHub Pages.

Why I Built This

vLLM is the best open-source inference engine I have used, and vllm serve exposes over two hundred flags. That is not a criticism — the surface area is the point. But it means the first hour with vLLM is spent reading a help text that cannot tell you the one thing you need to know, which is what your card can do.

The answers genuinely change per GPU. FP8 is the single best thing you can do on an RTX 4090 and simply does not exist on a 3090 — one generation apart. AWQ and GPTQ, the two formats every tutorial reaches for, have no ROCm kernels at all, so half the advice on the internet is wrong if you are on an MI300X. A V100 cannot run current vLLM at all, and the error you get does not say so in those words.

I had watched people — and myself — download 140 GB of weights to discover a configuration that was never going to load. The feedback loop is brutal: a typo in a flag costs you a model download and a ten minute startup before anything tells you it was wrong.

The Problem

Three things make vLLM configuration harder than it looks, and none of them are vLLM's fault.

The KV cache, not the weights, is usually what kills you. Everyone sizes a deployment by parameter count. But an 8B model in bfloat16 is about 15.6 GiB of weights — and at 5,120 tokens of context across 32 concurrent requests, its KV cache is 22.5 GiB. The part nobody budgets for is larger than the part everybody does.

Parameter counts are lies for Mixture-of-Experts models. A "30B-A3B" model advertises 3B active parameters. All 30B still sit in VRAM. Size for the active count and you will be out of memory by a factor of ten.

The flags move. Between the version I first wrote this against and today, --swap-space and --max-num-partial-prefills were removed outright, --async-scheduling and --disable-cascade-attn became defaults, bitsandbytes moved out of the main package into a plugin, and --kv-cache-dtype fp16 became float16. A generator that is not maintained against the current CLI produces commands that fail on startup.

How It Works

Nine modules, no framework, no build step. Every keystroke recomputes the whole estimate synchronously — it is arithmetic, and it is fast enough that debouncing would only add latency.

Architecture
model id ──▶ config-parser.js ──▶ huggingface.co/<id>/resolve/main/config.json
                     │                (CORS, read-only, public)
                     ▼
              parameter count, KV shape, detected quantization
                     │
     GPU preset ──▶ hardware.js ──▶ capability flags: bf16 / fp8 / fp4 / marlin
                     │                       │
                     ▼                       ▼
              estimator.js            validators.js         recommendRecipe()
           weights + KV + runtime    what will not run     what to use instead
                     │                       │                     │
                     └───────────┬───────────┴─────────────────────┘
                                 ▼
                    command.js ──▶ vllm serve …
                   artifacts.js ──▶ docker-compose.yml, .env, tests, README
                        zip.js ──▶ one download, no dependency

The centre of it is hardware.js: one table mapping thirteen GPU generations to what their kernels support. Everything else is derived from it — which quantizations appear as recommended, which are greyed out as unsupported, which Docker image you get, and what the tool suggests when your configuration does not fit. Adding a new card is one line; adding a new generation is one entry with its capability flags.

Counting Parameters Properly

The usual shortcut for model size is 12 × layers × hidden². It is off by several percent on dense models and badly wrong on everything else, which matters when the whole point is to tell someone whether 15.6 GiB fits in 24.

So the parser adds up the actual tensors: attention projections respecting grouped-query attention, the MLP or expert blocks, and embeddings counted once when the model ties them. For Mixture-of-Experts models it counts every expert, because every expert is resident.

Model Published size What the parser computes
Qwen3-8B 8.2 B 8.19 B
Mistral-7B-Instruct-v0.3 7.25 B 7.25 B
Llama-3.1-8B-Instruct 8.03 B 8.03 B

Those three cases are pinned in the test suite. If a change to the counting logic moves any of them, the build fails.

It Tells You What to Change

A verdict on its own is not much use. The more valuable output is the number underneath it — how many concurrent requests the leftover VRAM actually supports at the context length you asked for — and an ordered list of fixes, each with a button that applies it.

The results panel showing a memory breakdown and a recommendation to use FP8 for both weights and cache

The recommendation engine climbs a ladder and stops at the first rung that fits: nothing, then an FP8 cache, then FP8 weights, then 4-bit. It reaches for the cheapest change first, because halving the KV cache costs no quality while dropping the weights to 4-bit does.

Getting this right took a correction. The first version compared only the weights against total VRAM, so it would cheerfully print "no reason to quantize" next to a verdict that said "will not fit" — because the KV cache, the thing that actually overflowed, was not in the comparison. The engine now takes the cache as an input, and a test asserts the recommendation can never contradict the verdict.

The Hardware Table Is the Product

The part people bookmark is the cheat sheet: which generation supports what, and which quantization to reach for on each. It is generated from the same table the wizard uses, so the documentation cannot drift away from the behaviour.

The vLLM cheat sheet listing GPU generations against bfloat16, FP8 and 4-bit float support
Generation bfloat16 FP8 4-bit float Reach for
Volta — V100 No No No Unsupported by current vLLM
Turing — T4 No No No AWQ or GPTQ, and float16
Ampere — 3090, A100 Yes No No AWQ, GPTQ, compressed-tensors
Ada — 4090, L40S Yes Yes No FP8
Hopper — H100, H200 Yes Yes No FP8
Blackwell — 5090, B200 Yes Yes Yes NVFP4
CDNA 3 — MI300X Yes Yes No FP8 or Quark — not AWQ
CDNA 4 — MI355X Yes Yes Yes Quark, MXFP4

What It Generates

Six files, and the compose file is the one that earns its keep. Picking the wrong container image is the most common way a first deployment fails, so the generator branches on vendor: NVIDIA gets the usual device reservation block, AMD gets /dev/kfd and /dev/dri passed through plus group_add: video, Intel gets the render node, and CPU gets neither.

The generated docker-compose.yml for an NVIDIA deployment, with the image pinned and ipc host set
Hardware Image the generator picks
NVIDIA, CUDA 12.8+ vllm/vllm-openai
AMD, ROCm 6.3+ vllm/vllm-openai-rocm
Intel GPU vllm/vllm-openai-xpu
No GPU vllm/vllm-openai-cpu

Tags are pinned rather than :latest, the generated README explains every choice the wizard made, and the whole folder downloads as a zip written by about a hundred lines of zip.js — store-only entries, no compression library, no dependency.

Why I Deleted the Server

Versions 1 and 2 were a Flask app. Pydantic schemas, a SQLite history table, Jinja templates, nine JSON endpoints, a pytest suite. It worked. It was also, in hindsight, absurd: a tool whose entire purpose is telling you whether you can run something, that first requires you to clone a repo, make a virtualenv, install four packages and start a server on port 5000.

Nothing it did needed a server. The memory estimate is arithmetic. The command builder is string concatenation. The history was a list of small JSON blobs. So v3 deleted the backend entirely: every Python module was ported to a browser ES module, SQLite became localStorage, Jinja templates became template literals, and pytest became Node's built-in test runner.

v2 — Flask v3 — static
To use it clone, venv, pip install, run open a link
Model config download config.json, drag it in fetched from the Hub as you type
History SQLite on the server localStorage, per browser
Estimate latency debounced HTTP round trip synchronous, per keystroke
Dependencies Flask, Pydantic, Jinja2, pytest none
Hosting somebody's machine GitHub Pages

The migration also removed the feature I was most attached to and least willing to defend: the drag-and-drop config.json upload. Hugging Face serves config.json with permissive CORS headers, which I verified end to end before committing to the approach, so the browser can just read it. Typing Qwen/Qwen3-8B now does what dragging a downloaded file used to. The dropzone still exists, but only as the fallback for gated repos and local models — which is the only place it was ever really needed.

Tech Stack

Front end

Vanilla JavaScript ES Modules CSS custom properties No framework No build step

Browser APIs

fetch + CORS localStorage Blob & object URLs WebGL renderer info

Domain

vLLM v0.28 CLI Hugging Face Hub CUDA & ROCm FP8 / NVFP4 / AWQ / GPTQ Docker Compose

Engineering

node --test 99 tests, zero deps GitHub Actions GitHub Pages

How This Is Different

There are VRAM calculators, and there are config generators. The gap this fills is between them: a calculator tells you a number but not what to do about it, and a generator emits flags without checking whether your card can run them.

Easy-vLLM VRAM calculators vLLM docs
Counts MoE experts correctly Yes Usually not N/A
Knows what your GPU cannot run Yes — blocks it No Scattered across pages
Emits a runnable command Yes No Examples only
Picks the right container image Yes, per vendor No Manual
Tells you what to change Ordered fixes, one click No Prose
Needs anything installed No Varies N/A

What Shipped

My Contribution

Sole author. Design, the hardware capability model, the memory maths, the vLLM CLI research, the migration off Flask, the test suite and the CI. Easy-vLLM is a helper for vLLM, not a fork of it — all credit for the engine belongs to the vLLM project and its contributors.

Challenges & Learnings

A generator is only as good as its last upgrade. Auditing the current CLI turned up flags the old version was still emitting that vLLM had removed — the generated command would have failed at startup. The lesson was to make that class of bug testable: there is now a test that turns on every option at once and asserts that a list of removed flags never appears in the output. Correctness against a moving upstream has to be a fixture, not vigilance.

Advice that contradicts itself is worse than no advice. The recommendation engine printing "no reason to quantize" beside a "will not fit" verdict was the sharpest bug in the project, and it came from a genuine modelling error: two features answering the same question from different inputs. Making the verdict an input to the recommendation, rather than a parallel computation, is what fixed it structurally.

Test the restore path with non-default values. Saved settings restored fine in every check I ran, because every check used a single GPU. With four, the tensor-parallel value silently collapsed back to 1 — the dropdown was rebuilt with only one option before the saved value was written into it, so the assignment was dropped on the floor. Defaults hide entire categories of bug.

Browsers make you honest about failure. Private-browsing modes do not return empty from localStorage; they throw. Every read and write needed a guard, and the generate path needed a fallback so that someone with storage blocked still gets the files they just asked for rather than a confusing redirect.

Hugging Face answers 401 for repos that do not exist. It cannot say "no such repo" without leaking whether a private one exists, so a typo and a gated model look identical from outside. The error message has to cover both, and lead with the likelier one.

What's Next

A shareable-URL mode, so a configuration can be sent to a colleague as a link rather than described in a message. Then throughput estimates alongside the memory ones — tokens per second is the other question people ask, and it is harder, because it depends on kernels rather than arithmetic.

Longer term, the GPU table wants to be community-maintained. Adding a card is one line and adding a generation is one entry, which was a deliberate design goal: the long tail of hardware is not something one person can keep current.

Closing Note

The thing I would keep from this project is the deletion. Removing the server made the tool better on every axis that mattered — faster to use, faster to run, free to host, and impossible to have "broken locally". The Flask version was not wrong, but it charged a setup cost for a job that was always just arithmetic and a table.

Type a model, pick a card, get a command that works.

Resources & Links

Open the tool vLLM cheat sheet GitHub Repository

Tags:

LLM & RAG vLLM Vanilla JS GPU Memory Quantization Docker Open Source