Pros: Single endpoint, model zoo, CPU/GPU, fast setup, Mac M-series optimization Cons: Less tunable than raw vLLM/TGI; mixed plugin ecosystem Use when: Prototyping, local UX apps (OpenWebUI), mixed OS fleet
Pros: Smallest memory footprint per quality; insane quant variety; CPU-only feasible Cons: Lower throughput vs GPU servers; long context windows can get slow Use when: Edge/air-gapped, CPU-only nodes, deterministic packaging
Pros: PagedAttention = large context + strong throughput; OpenAI API out of the box Cons: GPU required; model weights usually FP16/BF16 or GPTQ/AWQ variants Use when: Serving, multi-tenant, latency targets
fromfastapiimportFastAPIimportrequests,uuid,jsonfromqdrant_clientimportQdrantClientfromqdrant_client.httpimportmodelsAPP=FastAPI()QDRANT=QdrantClient(url="http://qdrant:6333")COL="docs"EMB="http://e5-small:80/embed"OAI="http://vllm:8000/v1"MODEL="meta-llama/Meta-Llama-3-8B-Instruct"defembed(texts):r=requests.post(EMB,json={"inputs":texts})returnr.json()["embeddings"]@APP.post("/ingest")defingest(doc:dict):chunks=[doc["text"][i:i+800]foriinrange(0,len(doc["text"]),800)]vecs=embed(chunks)QDRANT.upsert(collection_name=COL,points=[models.PointStruct(id=str(uuid.uuid4()),vector=v,payload={"text":c})forv,cinzip(vecs,chunks)])return{"ok":True}@APP.post("/ask")defask(q:dict):qvec=embed([q["question"]])[0]hits=QDRANT.search(collection_name=COL,query_vector=qvec,limit=4)context="\n\n".join([h.payload["text"]forhinhits])prompt=f"Answer using only the context.\n\nContext:\n{context}\n\nQ: {q['question']}\nA:"r=requests.post(f"{OAI}/chat/completions",json={"model":MODEL,"messages":[{"role":"user","content":prompt}],"temperature":0.2})returnr.json()if__name__=="__main__":importuvicornuvicorn.run(APP,host="0.0.0.0",port=8010)
importos,time,requestsENDPOINT=os.getenv("OPENAI_ENDPOINT","http://localhost:8002/v1")MODEL=os.getenv("MODEL","meta-llama/Meta-Llama-3-8B-Instruct")prompt="Summarize: "+"lorem ipsum "*200print(f"Benchmarking {ENDPOINT} with model {MODEL}")# Latency testt0=time.time()r=requests.post(f"{ENDPOINT}/chat/completions",json={"model":MODEL,"messages":[{"role":"user","content":prompt}],"temperature":0.2,"max_tokens":512},timeout=120)dt=time.time()-t0out=r.json()["choices"][0]["message"]["content"]tps=512/dtprint(f"Latency: {dt:.2f}s ~{tps:.1f} tok/s len={len(out)}")# Throughput test (concurrent requests)importconcurrent.futuresimportthreadingdefsingle_request():t0=time.time()r=requests.post(f"{ENDPOINT}/chat/completions",json={"model":MODEL,"messages":[{"role":"user","content":"Hello"}],"temperature":0.2,"max_tokens":100},timeout=60)returntime.time()-t0# Run 5 concurrent requestswithconcurrent.futures.ThreadPoolExecutor(max_workers=5)asexecutor:futures=[executor.submit(single_request)for_inrange(5)]times=[f.result()forfinfutures]avg_time=sum(times)/len(times)print(f"Concurrent avg: {avg_time:.2f}s per request")
# Don't expose your local server to the internet naked# Use reverse proxy + authnginx-cnginx.conf
# Keep prompt/response logs off shared disks# If you run agents, rate-limit tools and cap budgets
# Easiest path: Ollama + OpenWebUIdockercompose--profileollamaup-d
curlhttp://localhost:11434/api/pull-d'{"name":"llama3.1:8b"}'# Open http://localhost:8080# Need OpenAI API + throughput: vLLM (GPU)dockercompose--profilevllmup-d
curlhttp://localhost:8002/v1/models
# CPU-only edge: llama.cpp with GGUF Q4_Kdockercompose--profilellamacppup-d
# Place model.gguf in ./models/# Add RAG: Qdrant + embeddings + demo APIdockercompose--profilerag--profilevllmup-d
curlhttp://localhost:8010/docs
# Benchmark, then raise quant quality until speed and output are both acceptabledockercompose--profilebenchup--build