Upload 4 files
Browse files- Dockerfile +79 -0
- README.md +125 -5
- entrypoint.sh +47 -0
- requirements.txt +5 -0
Dockerfile
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# syntax=docker/dockerfile:1
|
| 2 |
+
|
| 3 |
+
# ---------------------------------------------------------------------------
|
| 4 |
+
# Stage 1: build llama-server from source.
|
| 5 |
+
#
|
| 6 |
+
# IMPORTANT: tencent/Hy-MT2-7B-GGUF depends on a custom "STQ" ggml kernel
|
| 7 |
+
# that shipped in https://github.com/ggml-org/llama.cpp/pull/22836. A pip
|
| 8 |
+
# install of llama-cpp-python pins an older llama.cpp commit and will very
|
| 9 |
+
# likely NOT be able to load this GGUF (unknown tensor type / load error).
|
| 10 |
+
# Building llama.cpp from source here (default: the `main` branch, which
|
| 11 |
+
# should have the PR merged by now) avoids that. If it hasn't landed yet,
|
| 12 |
+
# rebuild with:
|
| 13 |
+
# docker build --build-arg LLAMA_CPP_REF=refs/pull/22836/head -t hy-mt2-api .
|
| 14 |
+
# ---------------------------------------------------------------------------
|
| 15 |
+
FROM debian:bookworm-slim AS builder
|
| 16 |
+
|
| 17 |
+
ARG LLAMA_CPP_REF=main
|
| 18 |
+
|
| 19 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 20 |
+
build-essential cmake git ca-certificates libcurl4-openssl-dev pkg-config \
|
| 21 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 22 |
+
|
| 23 |
+
WORKDIR /build
|
| 24 |
+
RUN git clone https://github.com/ggml-org/llama.cpp.git . \
|
| 25 |
+
&& if [ "$LLAMA_CPP_REF" != "main" ]; then \
|
| 26 |
+
git fetch origin "$LLAMA_CPP_REF" && git checkout FETCH_HEAD; \
|
| 27 |
+
fi
|
| 28 |
+
|
| 29 |
+
# GGML_NATIVE=OFF because the Docker build machine's CPU may differ from the
|
| 30 |
+
# machine the image eventually runs on; GGML_AVX2=ON targets the common
|
| 31 |
+
# baseline for modern cloud/Space CPUs. Drop it if you hit "illegal
|
| 32 |
+
# instruction" at runtime on older hardware.
|
| 33 |
+
RUN cmake -B build \
|
| 34 |
+
-DCMAKE_BUILD_TYPE=Release \
|
| 35 |
+
-DLLAMA_CURL=OFF \
|
| 36 |
+
-DGGML_NATIVE=OFF \
|
| 37 |
+
-DGGML_AVX2=ON \
|
| 38 |
+
&& cmake --build build --config Release -j"$(nproc)" --target llama-server
|
| 39 |
+
|
| 40 |
+
# ---------------------------------------------------------------------------
|
| 41 |
+
# Stage 2: runtime image (FastAPI gateway + llama-server binary)
|
| 42 |
+
# ---------------------------------------------------------------------------
|
| 43 |
+
FROM python:3.11-slim
|
| 44 |
+
|
| 45 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 46 |
+
libgomp1 libcurl4 curl \
|
| 47 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 48 |
+
|
| 49 |
+
WORKDIR /app
|
| 50 |
+
|
| 51 |
+
COPY --from=builder /build/build/bin/llama-server /app/llama-server
|
| 52 |
+
|
| 53 |
+
COPY requirements.txt .
|
| 54 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 55 |
+
|
| 56 |
+
COPY app ./app
|
| 57 |
+
COPY entrypoint.sh .
|
| 58 |
+
RUN chmod +x /app/llama-server /app/entrypoint.sh
|
| 59 |
+
|
| 60 |
+
ENV MODEL_REPO=tencent/Hy-MT2-7B-GGUF \
|
| 61 |
+
MODEL_FILE=Hy-MT2-7B-Q4_K_M.gguf \
|
| 62 |
+
MODEL_DIR=/app/models \
|
| 63 |
+
LLAMA_SERVER_HOST=127.0.0.1 \
|
| 64 |
+
LLAMA_SERVER_PORT=8080 \
|
| 65 |
+
THREADS=8 \
|
| 66 |
+
PARALLEL_SLOTS=8 \
|
| 67 |
+
CTX_SIZE=16384 \
|
| 68 |
+
MAX_TOKENS=512 \
|
| 69 |
+
TEMPERATURE=0.7 \
|
| 70 |
+
TOP_P=0.6 \
|
| 71 |
+
TOP_K=20 \
|
| 72 |
+
REPEAT_PENALTY=1.05 \
|
| 73 |
+
MAX_BATCH_SIZE=200 \
|
| 74 |
+
API_KEY="" \
|
| 75 |
+
PORT=7860
|
| 76 |
+
|
| 77 |
+
EXPOSE 7860
|
| 78 |
+
|
| 79 |
+
CMD ["./entrypoint.sh"]
|
README.md
CHANGED
|
@@ -1,10 +1,130 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
|
|
|
| 7 |
pinned: false
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Hy-MT2 Translation API
|
| 3 |
+
emoji: 🌐
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
+
license: apache-2.0
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Hy-MT2 Translation API
|
| 13 |
+
|
| 14 |
+
A batching-optimized translation API for [tencent/Hy-MT2-7B-GGUF](https://huggingface.co/tencent/Hy-MT2-7B-GGUF),
|
| 15 |
+
built to run as a Hugging Face Space Docker app on an 8 vCPU / 32 GB RAM
|
| 16 |
+
instance.
|
| 17 |
+
|
| 18 |
+
## How it works
|
| 19 |
+
|
| 20 |
+
- **Inference engine**: `llama-server` (from [llama.cpp](https://github.com/ggml-org/llama.cpp)),
|
| 21 |
+
compiled from source in the Docker build (see the note in the `Dockerfile`
|
| 22 |
+
about the STQ kernel this GGUF depends on — it needs a recent llama.cpp).
|
| 23 |
+
It's started with `--threads 8 --parallel 8 --cont-batching`, which uses
|
| 24 |
+
llama.cpp's continuous batching to combine multiple in-flight requests'
|
| 25 |
+
forward passes into shared compute instead of running them one at a time.
|
| 26 |
+
This is the real mechanism behind the "batch instead of one-by-one"
|
| 27 |
+
optimization you asked for — the 8 cores get fed one bigger batched matmul
|
| 28 |
+
per step instead of 8 independent tiny ones.
|
| 29 |
+
- **Gateway**: a small FastAPI app (`app/main.py`) in front of it, which
|
| 30 |
+
builds the Hy-MT2 instruction-format prompt (see `app/translation.py`) for
|
| 31 |
+
each string and fans batch requests out concurrently to `llama-server`,
|
| 32 |
+
bounded by `PARALLEL_SLOTS` so requests queue cleanly instead of
|
| 33 |
+
overloading the model.
|
| 34 |
+
- Chat formatting uses the GGUF's embedded Jinja chat template
|
| 35 |
+
(`llama-server --jinja`), matching the model card's documented usage.
|
| 36 |
+
|
| 37 |
+
## Endpoints
|
| 38 |
+
|
| 39 |
+
### `GET /health`
|
| 40 |
+
Backend status check.
|
| 41 |
+
|
| 42 |
+
### `GET /languages`
|
| 43 |
+
Returns the map of supported language codes → names (the 33 languages
|
| 44 |
+
Hy-MT2 documents support for).
|
| 45 |
+
|
| 46 |
+
### `POST /translate` — single string
|
| 47 |
+
```json
|
| 48 |
+
{
|
| 49 |
+
"text": "Where is the nearest potion shop?",
|
| 50 |
+
"target_lang": "tr",
|
| 51 |
+
"preserve_placeholders": true
|
| 52 |
+
}
|
| 53 |
+
```
|
| 54 |
+
```json
|
| 55 |
+
{ "translation": "En yakın iksir dükkanı nerede?", "elapsed_seconds": 0.41 }
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### `POST /translate/batch` — many strings in one call (recommended: 50-100 per call)
|
| 59 |
+
```json
|
| 60 |
+
{
|
| 61 |
+
"texts": ["Hello!", "Welcome!", "Potion", "Attack"],
|
| 62 |
+
"target_lang": "tr"
|
| 63 |
+
}
|
| 64 |
+
```
|
| 65 |
+
```json
|
| 66 |
+
{
|
| 67 |
+
"translations": [
|
| 68 |
+
{ "original": "Hello!", "translation": "Merhaba!" },
|
| 69 |
+
{ "original": "Welcome!", "translation": "Hoş geldin!" },
|
| 70 |
+
{ "original": "Potion", "translation": "İksir" },
|
| 71 |
+
{ "original": "Attack", "translation": "Saldırı" }
|
| 72 |
+
],
|
| 73 |
+
"count": 4,
|
| 74 |
+
"elapsed_seconds": 0.9,
|
| 75 |
+
"items_per_second": 4.44
|
| 76 |
+
}
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
For 50,000 game strings: split them into chunks of ~50-100 and call
|
| 80 |
+
`/translate/batch` per chunk (either sequentially or a few chunks at a time)
|
| 81 |
+
rather than one string per HTTP call. `MAX_BATCH_SIZE` (default 200) is a
|
| 82 |
+
server-side safety cap on a single call.
|
| 83 |
+
|
| 84 |
+
**Request fields common to both endpoints:**
|
| 85 |
+
| field | type | notes |
|
| 86 |
+
|---|---|---|
|
| 87 |
+
| `target_lang` | string | ISO code (`tr`, `en`, `ja`, ...) or full name |
|
| 88 |
+
| `source_lang` | string, optional | omit to let the model auto-detect |
|
| 89 |
+
| `style` | string, optional | e.g. `"casual, playful"` — injected as a style instruction |
|
| 90 |
+
| `glossary` | object, optional | `{"HP": "Can Puanı"}` — forces specific term translations |
|
| 91 |
+
| `preserve_placeholders` | bool, default `true` | instructs the model to never touch `\N[1]`, `\V[1]`, `{var}`, `%s`, `%1`, etc. — important for RPG Maker / game strings |
|
| 92 |
+
|
| 93 |
+
### Auth
|
| 94 |
+
Set the `API_KEY` env var on the Space to require an `X-API-Key` header on
|
| 95 |
+
`/translate*`. Leave empty (default) to disable auth.
|
| 96 |
+
|
| 97 |
+
## Configuration (env vars)
|
| 98 |
+
|
| 99 |
+
| var | default | meaning |
|
| 100 |
+
|---|---|---|
|
| 101 |
+
| `MODEL_FILE` | `Hy-MT2-7B-Q4_K_M.gguf` | switch to `HY-MT2-7B-Q6_K.gguf` or `HY-MT2-7B-Q8_0.gguf` for higher quality / slower |
|
| 102 |
+
| `THREADS` | `8` | CPU threads for llama-server |
|
| 103 |
+
| `PARALLEL_SLOTS` | `8` | concurrent generation slots (continuous batching) |
|
| 104 |
+
| `CTX_SIZE` | `16384` | total context, split across `PARALLEL_SLOTS` slots |
|
| 105 |
+
| `MAX_TOKENS` | `512` | max output tokens per translation |
|
| 106 |
+
| `MAX_BATCH_SIZE` | `200` | max items accepted per `/translate/batch` call |
|
| 107 |
+
| `TEMPERATURE`/`TOP_P`/`TOP_K`/`REPEAT_PENALTY` | `0.7`/`0.6`/`20`/`1.05` | Hy-MT2 7B recommended sampling params |
|
| 108 |
+
| `API_KEY` | *(empty)* | optional shared secret for `X-API-Key` |
|
| 109 |
+
|
| 110 |
+
## Deploying
|
| 111 |
+
|
| 112 |
+
1. On [huggingface.co/new-space](https://huggingface.co/new-space), choose
|
| 113 |
+
**Docker** as the SDK, then push this folder's contents (or upload via
|
| 114 |
+
the web UI / `huggingface_hub`).
|
| 115 |
+
2. In the Space's **Settings → Hardware**, select a CPU tier with 8 vCPU /
|
| 116 |
+
32 GB RAM (this needs to be set manually — it's a paid tier and isn't
|
| 117 |
+
controlled by any file in this repo).
|
| 118 |
+
3. First boot downloads the ~4.6 GB GGUF file, so the first request after a
|
| 119 |
+
cold start can take a few minutes; enable **persistent storage** in Space
|
| 120 |
+
settings to avoid re-downloading on every restart.
|
| 121 |
+
|
| 122 |
+
## Local testing
|
| 123 |
+
|
| 124 |
+
```bash
|
| 125 |
+
docker build -t hy-mt2-api .
|
| 126 |
+
docker run -p 7860:7860 --cpus 8 --memory 32g hy-mt2-api
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
Then open `test-ui/index.html` in a browser, point it at
|
| 130 |
+
`http://localhost:7860`, and use it to benchmark single vs. batch requests.
|
entrypoint.sh
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
MODEL_PATH="${MODEL_DIR}/${MODEL_FILE}"
|
| 5 |
+
|
| 6 |
+
if [ ! -f "$MODEL_PATH" ]; then
|
| 7 |
+
echo "[entrypoint] Downloading ${MODEL_FILE} from ${MODEL_REPO} ..."
|
| 8 |
+
python -c "
|
| 9 |
+
from huggingface_hub import hf_hub_download
|
| 10 |
+
hf_hub_download(repo_id='${MODEL_REPO}', filename='${MODEL_FILE}', local_dir='${MODEL_DIR}')
|
| 11 |
+
"
|
| 12 |
+
else
|
| 13 |
+
echo "[entrypoint] Found cached model at ${MODEL_PATH}"
|
| 14 |
+
fi
|
| 15 |
+
|
| 16 |
+
echo "[entrypoint] Starting llama-server (threads=${THREADS}, parallel=${PARALLEL_SLOTS}, ctx=${CTX_SIZE}) ..."
|
| 17 |
+
/app/llama-server \
|
| 18 |
+
--model "$MODEL_PATH" \
|
| 19 |
+
--host "${LLAMA_SERVER_HOST}" \
|
| 20 |
+
--port "${LLAMA_SERVER_PORT}" \
|
| 21 |
+
--threads "${THREADS}" \
|
| 22 |
+
--threads-batch "${THREADS}" \
|
| 23 |
+
--ctx-size "${CTX_SIZE}" \
|
| 24 |
+
--parallel "${PARALLEL_SLOTS}" \
|
| 25 |
+
--cont-batching \
|
| 26 |
+
--jinja \
|
| 27 |
+
--no-webui &
|
| 28 |
+
LLAMA_PID=$!
|
| 29 |
+
|
| 30 |
+
echo "[entrypoint] Waiting for llama-server to become healthy ..."
|
| 31 |
+
for _ in $(seq 1 150); do
|
| 32 |
+
if curl -sf "http://${LLAMA_SERVER_HOST}:${LLAMA_SERVER_PORT}/health" >/dev/null 2>&1; then
|
| 33 |
+
echo "[entrypoint] llama-server is ready."
|
| 34 |
+
break
|
| 35 |
+
fi
|
| 36 |
+
if ! kill -0 "$LLAMA_PID" 2>/dev/null; then
|
| 37 |
+
echo "[entrypoint] llama-server exited unexpectedly, aborting." >&2
|
| 38 |
+
exit 1
|
| 39 |
+
fi
|
| 40 |
+
sleep 2
|
| 41 |
+
done
|
| 42 |
+
|
| 43 |
+
echo "[entrypoint] Starting API gateway on port ${PORT} ..."
|
| 44 |
+
uvicorn app.main:app --host 0.0.0.0 --port "${PORT}" &
|
| 45 |
+
API_PID=$!
|
| 46 |
+
|
| 47 |
+
wait -n "$LLAMA_PID" "$API_PID"
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.115.6
|
| 2 |
+
uvicorn[standard]==0.34.0
|
| 3 |
+
httpx==0.28.1
|
| 4 |
+
pydantic==2.10.5
|
| 5 |
+
huggingface_hub==0.27.1
|