Spaces:
Sleeping
Sleeping
| title: GGUF Chat | |
| emoji: π§ | |
| colorFrom: indigo | |
| colorTo: purple | |
| sdk: docker | |
| app_port: 7860 | |
| pinned: false | |
| license: apache-2.0 | |
| # GGUF Chat (Docker Space) | |
| A self-contained Hugging Face **Docker Space** that downloads a GGUF quant of | |
| [`bartowski/google_gemma-3-2b-it-GGUF`](https://huggingface.co/bartowski/google_gemma-3-2b-it-GGUF) | |
| and serves it through a Gradio chat UI β with a live download/load progress | |
| bar on first request, then token-by-token streaming β powered by | |
| [`llama-cpp-python`](https://github.com/abetlen/llama-cpp-python). | |
| The default model is a small (~2B parameter) instruct model chosen | |
| specifically to run comfortably on the **free CPU-basic Spaces tier**. Swap | |
| in a different repo any time via the `GGUF_REPO_ID` environment variable β | |
| no code changes needed. | |
| --- | |
| ## What's in this repo | |
| | File | Purpose | | |
| |-------------------|-----------------------------------------------------------------------| | |
| | `Dockerfile` | Multi-stage build: tries a prebuilt CPU wheel for `llama-cpp-python` first, falling back to compiling it in a builder stage; ships a slim runtime image with no compilers. | | |
| | `requirements.txt`| Pure-Python runtime deps (`gradio`, `huggingface_hub`). | | |
| | `app.py` | Downloads/caches the GGUF, loads it with `llama-cpp-python`, and serves the Gradio UI (progress bar + streaming chat). | | |
| | `README.md` | This file (also the Space's metadata card, via the YAML frontmatter above). | | |
| This repo is ready to push directly to a new **Docker** Space with no | |
| further edits. | |
| --- | |
| ## A note on the free Spaces tier | |
| This project is tuned to actually work on the **free CPU basic** tier | |
| end-to-end, which comes with real constraints worth knowing up front: | |
| - **No persistent disk by default.** Model weights re-download on every | |
| full restart/rebuild (see "Model caching" below) unless you pay for the | |
| Persistent Storage add-on. | |
| - **2 vCPUs, 16GB RAM.** `N_THREADS` defaults to `2` to match the actual | |
| core count rather than over-subscribing; `N_CTX` defaults to a modest | |
| `4096` tokens, well within what a ~1B `Q4_K_M` model needs on this | |
| hardware. | |
| - **Build minutes are shared/limited infrastructure**, so the Dockerfile | |
| tries a prebuilt wheel for `llama-cpp-python` before ever compiling from | |
| source (see "Technical notes" below) β this is the single biggest lever | |
| for keeping first-time build time low on the free tier. | |
| - If you outgrow any of this β a bigger model, longer context β the fixes | |
| are all opt-in: upgrade to a paid CPU/GPU hardware tier, or add | |
| Persistent Storage, and adjust `N_CTX`/`N_THREADS` accordingly. No code | |
| changes required, since everything is environment-variable driven. | |
| ## Deploying to Hugging Face Spaces | |
| 1. Create a new Space at <https://huggingface.co/new-space>. | |
| 2. Choose **Docker** as the Space SDK (not "Gradio" or "Streamlit" β this | |
| project builds and runs its own Dockerfile). | |
| 3. Pick the **CPU basic (free)** hardware tier β this project is built to | |
| run entirely on CPU. | |
| 4. Push these four files to the Space repo: | |
| ```bash | |
| git clone https://huggingface.co/spaces/<your-username>/<your-space-name> | |
| cd <your-space-name> | |
| cp /path/to/Dockerfile /path/to/requirements.txt /path/to/app.py /path/to/README.md . | |
| git add . | |
| git commit -m "Deploy GGUF chat Space" | |
| git push | |
| ``` | |
| 5. The Space will build the Docker image (typically well under a minute for | |
| `llama-cpp-python` if a prebuilt wheel is available β see "Technical | |
| notes" β otherwise 15-30+ minutes compiling from source) and then start | |
| the container. On first chat message, `app.py` downloads the selected | |
| GGUF file from the Hub and shows a live progress bar in the UI itself | |
| (in addition to the Space's **Logs** tab). | |
| 6. Once the model finishes loading, replies stream in token-by-token. | |
| No secrets or tokens are required for the default (public) repo. If you | |
| point this at a **gated/private** GGUF repo, add an `HF_TOKEN` secret in the | |
| Space's **Settings β Variables and secrets**; `huggingface_hub` picks it up | |
| automatically. | |
| --- | |
| ## Configuration (environment variables) | |
| All of these are set with sensible defaults in the `Dockerfile` and can be | |
| overridden per-Space under **Settings β Variables and secrets** without | |
| touching any code: | |
| | Variable | Default | Description | | |
| |-----------------------|-------------------------------------------------------|--------------| | |
| | `GGUF_REPO_ID` | `bartowski/google_gemma-3-2b-it-GGUF` | Hub repo to pull the GGUF from. | | |
| | `GGUF_FILENAME` | *(empty = auto-select)* | Force an exact filename instead of auto-selecting by quant. | | |
| | `PREFERRED_QUANT` | `Q4_K_M` | Preferred quantization. Falls back automatically (`Q4_K_S` β `Q5_K_M` β ... β smallest available `.gguf`) if not present. `mmproj` (vision) and `-MTP-` (speculative-decoding draft head) files are skipped by the auto-selector in favor of a plain text-chat quant. | | |
| | `MODEL_CACHE_DIR` | `/data/models` | Local cache directory for downloaded model weights. | | |
| | `HF_HOME` | `/data/hf_home` | Cache directory for Hub metadata. | | |
| | `N_CTX` | `4096` | Context window (tokens) allocated at load time. Raise this if you swap in a model that needs more headroom and you have the RAM to back it. | | |
| | `N_THREADS` | `2` | CPU threads for inference. Matches the free tier's 2 vCPUs by default; raise it if you upgrade hardware. | | |
| | `N_BATCH` | `256` | Prompt processing batch size. | | |
| | `MAX_NEW_TOKENS` | `899` | Max tokens generated per reply. | | |
| | `TEMPERATURE` | `0.7` | Sampling temperature. | | |
| | `TOP_P` | `0.9` | Nucleus sampling. | | |
| | `TOP_K` | `40` | Top-k sampling. | | |
| | `REPEAT_PENALTY` | `1.1` | Repetition penalty. | | |
| | `SYSTEM_PROMPT` | *(a short "be concise, adapt to the request" prompt β see `app.py`)* | System prompt prepended to every conversation. Override to customize the assistant's behavior. | | |
| | `DOWNLOAD_MAX_RETRIES` | `5` | Retry attempts (exponential backoff) for the model download. | | |
| --- | |
| ## Model caching & the free tier's storage caveat | |
| `app.py` downloads the model once into `MODEL_CACHE_DIR` and reuses the | |
| cached file for every subsequent chat request β it will **not** re-download | |
| on every message, and it survives the container going to sleep/waking back | |
| up from inactivity. | |
| However, **the free Spaces tier has no *persistent* storage**: the | |
| container's disk (including `/data`) is rebuilt from scratch whenever the | |
| Space is fully **restarted or rebuilt** (e.g. after a `git push`, a factory | |
| reboot, or an infrastructure migration). In that case, the model will be | |
| re-downloaded once on the next startup β this is a platform limitation, not | |
| a bug in this app. If you need the cache to survive restarts, enable | |
| **Persistent Storage** for the Space (a paid add-on) and point | |
| `MODEL_CACHE_DIR`/`HF_HOME` at the mounted persistent volume (typically | |
| `/data`, which is already the default here). | |
| --- | |
| ## Local development (outside Docker) | |
| ```bash | |
| # Build llama-cpp-python with a build appropriate for your machine: | |
| CMAKE_ARGS="-DGGML_NATIVE=ON" pip install llama-cpp-python | |
| pip install -r requirements.txt | |
| python app.py | |
| # then open http://localhost:7860 | |
| ``` | |
| ## Building/running the Docker image locally | |
| ```bash | |
| docker build -t gguf-chat-space . | |
| docker run -it -p 7860:7860 gguf-chat-space | |
| # then open http://localhost:7860 | |
| ``` | |
| --- | |
| ## Technical notes | |
| - **`llama-cpp-python` install strategy**: the builder stage first tries to | |
| fetch a **prebuilt CPU wheel** from the maintainer's custom index | |
| (`https://abetlen.github.io/llama-cpp-python/whl/cpu`) using | |
| `pip wheel --only-binary=:all:`, which fails fast (rather than silently | |
| falling back to a slow source build) if no matching wheel exists for the | |
| image's Python ABI/platform. This turns the install from a **15-30+ | |
| minute from-source compile into a roughly one-minute download** on the | |
| common case. | |
| Only if no prebuilt wheel is available does the Dockerfile fall back to | |
| building from source, with `CMAKE_ARGS="-DGGML_NATIVE=OFF -DGGML_AVX2=ON | |
| -DGGML_FMA=ON -DGGML_F16C=ON"`. `GGML_NATIVE` is deliberately disabled | |
| because the machine that *builds* the Docker image is not guaranteed to | |
| be the same CPU that *runs* it; auto-detected "native" builds can | |
| otherwise crash with `SIGILL` on the Space's actual runner. AVX2/FMA/F16C | |
| are supported by essentially all modern cloud x86_64 CPUs and give good | |
| performance without that risk. The compiler toolchain (`build-essential`, | |
| `cmake`, `ninja-build`, `git`) is only installed in this fallback branch, | |
| and the fallback build caps itself at 4 parallel compile jobs to avoid | |
| getting OOM-killed on the free Spaces builder, which can report more CPU | |
| cores than it has RAM to back a fully parallel C++ build. | |
| - **Chat template**: the `Llama` object is created without a hardcoded | |
| `chat_format`, so `llama-cpp-python` auto-detects and applies the Jinja | |
| chat template embedded in the GGUF's own metadata. This is what lets the | |
| same `app.py` work correctly across different model families (Gemma, | |
| Llama, Qwen, ...) if you change `GGUF_REPO_ID`, without hardcoding any | |
| model-specific prompt formatting. | |
| - **Progress bar UI**: the model downloads/loads lazily on the *first* chat | |
| request rather than at container startup, and `app.py` streams a live | |
| HTML progress bar (download %, then an animated layer-load indicator) | |
| into the response pane while that happens, in addition to logging | |
| progress to the Space's **Logs** tab. The layer-load bar is a smooth | |
| "still working" animation rather than an exact per-layer readout, since | |
| `llama-cpp-python`'s Python API doesn't expose real-time load progress. | |
| - **Streaming**: implemented via `llm.create_chat_completion(..., stream=True)`, | |
| yielding incrementally-growing text into the UI for token-by-token display. | |
| - **GPU layers**: `n_gpu_layers=0` β this Space is CPU-only by design, matching | |
| the free Spaces hardware tier. | |
| - **File selection**: uses `huggingface_hub.HfApi().model_info(..., files_metadata=True)` | |
| to inspect all files with sizes, filters out `mmproj` (vision projector) | |
| and `-MTP-` (speculative decoding draft-head) variants by default, then | |
| picks the smallest file matching `PREFERRED_QUANT`, falling back through | |
| a quant-quality-ordered list if needed. |