Spaces:
Sleeping
Sleeping
File size: 11,163 Bytes
2cd10cc 9a324c2 9ae8086 2cd10cc 9ae8086 2cd10cc 9ae8086 2cd10cc 9a324c2 9ae8086 9a324c2 9ae8086 9a324c2 9ae8086 9a324c2 9ae8086 9a324c2 9ae8086 5e0c7a6 9a324c2 5e0c7a6 9a324c2 5e0c7a6 9ae8086 9a324c2 9ae8086 9a324c2 9ae8086 9a324c2 9ae8086 9a324c2 9ae8086 9a324c2 9ae8086 9a324c2 9ae8086 5e0c7a6 9ae8086 9a324c2 9ae8086 9a324c2 9ae8086 9a324c2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | ---
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. |