Spaces:
Running on Zero
A newer version of the Gradio SDK is available: 6.22.0
Lessons Learned — Dead Ends & Disproven Approaches
Read this before pursuing performance or architecture changes. Each entry records what was tried, why it failed, and when it might be worth revisiting. Detailed reasoning lives in
docs/archive/.
Transformers + dedicated GPU for workshop load
Status: Disproven for >1 concurrent user.
Outcome: 0.18 req/s, 25% success rate at 15 concurrent users on a dedicated T4. GPU utilization stuck at ~17%.
Why it failed:
- Python/GIL contention in the
model.generate()loop serializes threads under concurrent load. - Per-token Python overhead (logit processing, sampling, prompt re-packing) dominates; CUDA kernels are fast but the host can't feed them fast enough.
- Each additional Gradio worker thread makes every other thread slower, until long generations stall past the timeout.
Detail: docs/archive/profiling-analysis.md §3-5.
When to revisit: Never, unless the generation loop moves out of Python (vLLM, process isolation, or a compiled backend that actually works under multithreading).
torch.compile
Status: Disproven — 44% throughput regression.
Outcome: 0.10 req/s with compile on vs 0.18 req/s off (L4, 15-user stress test).
Why it failed:
- Cudagraphs crash under Gradio's multithreaded handlers — Inductor stores cudagraph tree state in thread-local storage, so worker threads trip an
AssertionError. Without cudagraphs,reduce-overheadloses most of its benefit for tiny per-token kernels. dynamic=True+ varying prompt lengths means each new shape pays recompilation cost; at classroom scale those first calls dominate.- Models are so small (270m-1B) that kernel execution time is already tiny; Dynamo guard + Inductor codegen dispatch overhead outweighs fusion benefit.
Detail: docs/archive/profiling-analysis.md §"torch.compile measured regression" addendum.
When to revisit: Only if the threading model changes (single-threaded inference) or the model size grows large enough that kernel fusion matters.
Quantization (INT8 / AWQ / FP8)
Status: Disproven — wrong bottleneck.
Outcome: GPU is already idle at 17% utilization. Reducing weight size/compute does not help when the bottleneck is host-side Python overhead.
Why it failed:
- Quantization reduces memory bandwidth and VRAM footprint, but the GPU is not memory-bound or compute-bound — it's starved by the Python dispatch loop.
- Pre-quantized checkpoints exist only for some model variants (1B instruct); no coverage for base or 270m.
- Community checkpoints introduce quality, tokenizer, and dependency risks.
Detail: docs/archive/pre-quantized-models-assessment.md.
When to revisit: Only as part of a vLLM migration where quantized weights are paired with a continuous-batching engine that can exploit them.
Raising GRADIO_CONCURRENCY on Transformers
Status: Disproven — triggers GIL collapse.
Outcome: At concurrency=4, 75% failure rate (all long-generation requests timed out). At concurrency=1, 100% success but serial.
Why it failed:
- GIL contention is not a mild slowdown — it's a catastrophic collapse. Each additional thread makes every thread slower until long generations stall past the 30s timeout.
- The correct fix is to reduce per-request server time, not increase concurrency.
Detail: docs/archive/profiling-analysis.md §5.
When to revisit: Never on the Transformers backend. vLLM's continuous batching handles concurrency without Python-level contention.
CUDA_LAUNCH_BLOCKING=1
Status: Disproven — made things 10x worse.
Outcome: Next-token forward pass went from 0.18s to ~2.0s. All streaming requests timed out. GPU util did not rise.
Why it failed:
- Blocking removes the CPU/GPU overlap that was partly masking the GIL problem.
- A 270m forward/decode is hundreds of tiny kernels; blocking forces a CPU↔GPU sync on each one.
- GPU util stayed flat at ~10-17%, confirming the GPU was not secretly busy.
Detail: docs/archive/profiling-analysis.md §4.
When to revisit: Never. The diagnosis is closed: async dispatch was helping, not hurting.
vLLM on T4 (Turing)
Status: Incompatible.
Outcome: vLLM 0.24 V1 engine requires Ampere+ (compute 8.0). T4 is Turing (compute 7.5).
Why it failed:
- V1 engine requires Ampere+ for bf16, FlashAttention 2, and Triton shared memory.
- No workaround; T4 is fundamentally below the floor.
Detail: docs/archive/vllm-spike-results.md §"Hardware note".
When to revisit: Only if vLLM adds Turing support (unlikely) or you move to Ampere+ hardware.
vLLM on CPU (0.24 pinned)
Status: Not viable with current pin.
Outcome: The standard vllm==0.24.0 wheel is GPU-only.
Why it failed:
- vLLM's CPU backend is experimental with no official prebuilt wheels.
- Requires a third-party
vllm-cpuPyPI package or a source build. - Needs AVX512-class instructions (
avx512_bf16/avx512_vnni) for usable performance; plain x86-64 crawls. - The
AsyncLLMEngineAPI used in this repo is untested on CPU.
Detail: This spec's research (2026-07-08).
When to revisit: If vLLM CPU support matures to official wheels and the AsyncLLMEngine path is validated on CPU.
ZeroGPU + vLLM
Status: Fundamentally incompatible.
Why it fails:
- vLLM engines are long-lived processes that grab VRAM at construction and hold persistent state.
- ZeroGPU allocates GPU per-call from a shared pool with short-lived allocation windows.
- They cannot coexist architecturally.
Detail: AGENTS.md history; commit 55d2416.
When to revisit: Never. This is an architectural incompatibility, not a version issue.
Process isolation / multiprocessing
Status: Considered, deprioritized.
Outcome: Would remove GIL contention but at high complexity for modest gain.
Why it was passed over:
- Adds IPC and streaming complexity for small models.
- Requires duplicating model weights per process or implementing shared-memory sharing.
- Does not solve the "too many tiny kernel calls" problem — only makes them contend less.
- vLLM solves both the GIL/contention problem AND the tiny-kernel problem, making it the better investment.
Detail: docs/archive/performance-path-forward.md.
When to revisit: If vLLM is blocked by deployment restrictions and throughput must improve.
ZeroGPU workshop load capacity
Status: Resolved — measured 2026-07-12. ZeroGPU is viable for a ~15-user workshop.
Run: scripts/stress_test.py --users 15 --mode realistic --duration 180 against the deployed Space (rectified-snugness/glorified-spellcheck, ZeroGPU H200, Transformers backend). 100 requests / 187 s wall.
Outcome: 0.53 req/s, 100% success (0 timeouts, 0 errors, 0 rate-limited). Per-endpoint client latency:
| Endpoint | p50 | p95 | p99 |
|---|---|---|---|
next_token |
5.4 s | 12.8 s | 14.8 s |
compare_models |
8.6 s | 28.6 s | 32.7 s |
layering |
8.6 s | 33.0 s | 37.9 s |
Interpretation:
- Reliability fixed vs the T4 baseline (
docs/archive/profiling-analysis.md): 25% → 100% success and 0.18 → 0.53 req/s (~3×). The H200 is fast enough that even GIL-contended generations finish withinDEFAULT_TIMEOUT, so the collapse that crippled the T4 no longer causes failures. - The GIL still caps throughput at ~0.53 req/s — roughly 7× below vLLM's dedicated-L4 ceiling (3.63 req/s,
docs/archive/vllm-spike-results.md). ZeroGPU did not remove the Python bottleneck; it stopped it from breaking requests. vLLM remains a ZeroGPU dead-end (dedicated GPU only), so this gap is the accepted trade-off. next_tokenlatency is wait-bound, not compute-bound: its forward pass is ~21 ms warm but p50 is 5.4 s — the gap is queueing behind slow generator calls plus ZeroGPU's per-call GPU-grant overhead. On a dedicated GPU this lightweight endpoint interleaved cheaply (0.5 s p50); on ZeroGPU every@GPUcall pays a fixed allocation cost that dominates a 21 ms forward pass.
Reproduce: scripts/stress_test.py --url <owner/name-or-*.hf.space-app-url> --users 15 --mode realistic --duration 180 (not the huggingface.co/spaces/... Hub page URL — gradio_client cannot fetch config from it).
When to revisit: Before any workshop larger than 15 users, or if p95 latency (30 s on the generators) becomes painful. This single run exceeds the free tier's 300 GPU-s/day budget; a PRO account (2400 s/day) absorbs it with headroom for a few repeats. If throughput must improve, the only remaining lever is vLLM on a dedicated GPU (archive/vllm-backend branch + docs/archive/vllm-*.md); the GIL ceiling cannot be raised on ZeroGPU.