Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files
Dockerfile
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM nvidia/cuda:12.8.0-cudnn-devel-ubuntu24.04
|
| 2 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 3 |
+
|
| 4 |
+
ARG HF_TOKEN
|
| 5 |
+
|
| 6 |
+
ENV HF_TOKEN=$HF_TOKEN
|
| 7 |
+
RUN rm -rf /usr/local/bin /usr/local/lib* || true
|
| 8 |
+
RUN ln -s /usr/bin /usr/local/bin && ln -s /usr/lib /usr/local/lib && ln -s /usr/lib /usr/local/lib64
|
| 9 |
+
RUN apt-get update && apt-get install -y
|
| 10 |
+
RUN apt-get update && \
|
| 11 |
+
apt-get upgrade -y
|
| 12 |
+
RUN apt-get install -y --no-install-recommends --fix-missing \
|
| 13 |
+
git \
|
| 14 |
+
git-lfs \
|
| 15 |
+
wget \
|
| 16 |
+
curl \
|
| 17 |
+
cmake \
|
| 18 |
+
build-essential \
|
| 19 |
+
libssl-dev \
|
| 20 |
+
zlib1g-dev \
|
| 21 |
+
libbz2-dev \
|
| 22 |
+
libreadline-dev \
|
| 23 |
+
libsqlite3-dev \
|
| 24 |
+
libncursesw5-dev \
|
| 25 |
+
xz-utils \
|
| 26 |
+
tk-dev \
|
| 27 |
+
libxml2-dev \
|
| 28 |
+
libxmlsec1-dev \
|
| 29 |
+
libffi-dev \
|
| 30 |
+
golang-go \
|
| 31 |
+
python3 \
|
| 32 |
+
liblzma-dev \
|
| 33 |
+
ffmpeg \
|
| 34 |
+
nvidia-driver-570 \
|
| 35 |
+
python3 \
|
| 36 |
+
python3-pip unzip curl original-awk grep sed zstd
|
| 37 |
+
|
| 38 |
+
WORKDIR /app
|
| 39 |
+
COPY --chown=1000 . /app
|
| 40 |
+
RUN mkdir /app -p && chmod 777 /app
|
| 41 |
+
|
| 42 |
+
# RUN bash instollama.sh # Currently all model types are supported no need to build
|
| 43 |
+
|
| 44 |
+
RUN curl -fsSL https://ollama.com/install.sh | sh
|
| 45 |
+
|
| 46 |
+
# RUN cd /app && \
|
| 47 |
+
# git clone --recursive https://github.com/ollama/ollama.git && \
|
| 48 |
+
# cd ollama && \
|
| 49 |
+
# go generate ./... && \
|
| 50 |
+
# go build . && \
|
| 51 |
+
# ln -s $PWD/ollama /usr/bin/ollama && \
|
| 52 |
+
# chmod +x ollama && \
|
| 53 |
+
# cd ..
|
| 54 |
+
|
| 55 |
+
# RUN cd /app && \
|
| 56 |
+
# git clone --recursive https://github.com/ggerganov/llama.cpp && \
|
| 57 |
+
# cd llama.cpp && \
|
| 58 |
+
# cmake -B build -DBUILD_SHARED_LIBS=OFF -DGGML_CUDA=OFF -DLLAMA_CURL=OFF && \
|
| 59 |
+
# cmake --build build --config Release -j --target llama-quantize --parallel 12 && \
|
| 60 |
+
# cp ./build/bin/llama-* /usr/bin/ && \
|
| 61 |
+
# cp convert_hf_to_gguf.py /usr/bin/convert_hf_to_gguf && \
|
| 62 |
+
# rm -rf build && \
|
| 63 |
+
# cd ..
|
| 64 |
+
# RUN id -u 1000 &>/dev/null || useradd -m -u 1000 user
|
| 65 |
+
# USER 1000
|
| 66 |
+
# ENV HOME=/home/user \
|
| 67 |
+
# PATH=/home/user/.local/bin:${PATH}
|
| 68 |
+
WORKDIR /app
|
| 69 |
+
# RUN curl https://pyenv.run | bash
|
| 70 |
+
# ENV PATH=${HOME}/.pyenv/shims:${HOME}/.pyenv/bin:${PATH}
|
| 71 |
+
# ARG PYTHON_VERSION=3.13
|
| 72 |
+
# RUN pyenv install ${PYTHON_VERSION} && \
|
| 73 |
+
# pyenv global ${PYTHON_VERSION} && \
|
| 74 |
+
# pyenv rehash
|
| 75 |
+
RUN pip install --no-cache-dir -U pip setuptools wheel --break-system-packages --ignore-installed
|
| 76 |
+
RUN pip install "huggingface-hub" "hf-transfer" "gradio[oauth]>=6.5.1" "APScheduler" "protobuf>=4.21.0,<5.0.0" "sentencepiece>=0.1.98,<0.3.0" "numpy~=1.26.4" "gguf>=0.1.0" "fastapi" --break-system-packages --ignore-installed
|
| 77 |
+
|
| 78 |
+
RUN pip install "torch>=2.8.0" --break-system-packages --ignore-installed
|
| 79 |
+
RUN pip install git+https://github.com/huggingface/transformers.git --break-system-packages --ignore-installed
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
RUN mkdir /tmp/llama && hf download lainlives/llama.cpp --local-dir /tmp/llama && chmod +x /tmp/llama/* && cp /tmp/llama/convert* /app/convert_hf_to_gguf.py && mv /tmp/llama/* /usr/bin/
|
| 84 |
+
|
| 85 |
+
ENV PYTHONPATH=${HOME}/app \
|
| 86 |
+
PYTHONUNBUFFERED=1 \
|
| 87 |
+
HF_HUB_ENABLE_HF_TRANSFER=1 \
|
| 88 |
+
GRADIO_ALLOW_FLAGGING=never \
|
| 89 |
+
GRADIO_NUM_PORTS=1 \
|
| 90 |
+
GRADIO_SERVER_NAME=0.0.0.0 \
|
| 91 |
+
GRADIO_ANALYTICS_ENABLED=False \
|
| 92 |
+
TQDM_POSITION=-1 \
|
| 93 |
+
TQDM_MININTERVAL=1 \
|
| 94 |
+
SYSTEM=spaces \
|
| 95 |
+
LD_LIBRARY_PATH=/usr/local/cuda/lib64:${LD_LIBRARY_PATH} \
|
| 96 |
+
PATH=/usr/local/nvidia/bin:${PATH}
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
EXPOSE 7860
|
| 102 |
+
ENTRYPOINT python3 /app/app.py
|
README.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk:
|
| 7 |
-
sdk_version: 6.9.0
|
| 8 |
-
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
-
|
| 12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: HF to Ollama-currently indev
|
| 3 |
+
emoji: 📈
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: pink
|
| 6 |
+
sdk: docker
|
|
|
|
|
|
|
| 7 |
pinned: false
|
| 8 |
+
suggested_hardware: "a10g-large"
|
| 9 |
+
disable_embedding: true
|
| 10 |
---
|
|
|
|
|
|
app.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
from sys import argv
|
| 4 |
+
from time import strftime, sleep
|
| 5 |
+
import shutil
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import signal
|
| 9 |
+
from huggingface_hub import snapshot_download, HfApi
|
| 10 |
+
from apscheduler.schedulers.background import BackgroundScheduler
|
| 11 |
+
from theme import blurple
|
| 12 |
+
|
| 13 |
+
# Used for restarting the space
|
| 14 |
+
HF_TOKEN = os.environ.get("HF_TOKEN")
|
| 15 |
+
TEST_OKEY = os.environ.get("TEST_OKEY")
|
| 16 |
+
TEST_TOKEN = os.environ.get("HF_TOKEN")
|
| 17 |
+
HOST_REPO = "lainlives/ztestzz"
|
| 18 |
+
LLAMACPP_DIR = Path("./llama.cpp")
|
| 19 |
+
CONVERT_SCRIPT = "/app/convert_hf_to_gguf.py"
|
| 20 |
+
QUANTIZE_BIN = "llama-quantize"
|
| 21 |
+
TARGET_QUANTS = ["Q8_0", "Q6_K", "Q5_K_M", "Q5_K_S", "Q5_0", "Q4_K_M", "Q4_K_S", "Q4_0"]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def format_log(msg):
|
| 25 |
+
return f"[{strftime('%H:%M:%S')}] {msg}"
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def setup_ollama_keys(private_key_content):
|
| 29 |
+
"""
|
| 30 |
+
Writes the user's private key to ~/.ollama/id_ed25519
|
| 31 |
+
"""
|
| 32 |
+
if not private_key_content:
|
| 33 |
+
return False, "⚠️ No Private Key provided. Pushing will fail."
|
| 34 |
+
|
| 35 |
+
ollama_dir = Path(os.path.expanduser("~/.ollama"))
|
| 36 |
+
ollama_dir.mkdir(parents=True, exist_ok=True)
|
| 37 |
+
|
| 38 |
+
key_path = ollama_dir / "id_ed25519"
|
| 39 |
+
os.remove(key_path)
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
# Write the key
|
| 43 |
+
with open(key_path, "w") as f:
|
| 44 |
+
f.write(private_key_content.strip())
|
| 45 |
+
os.chmod(key_path, 0o600)
|
| 46 |
+
|
| 47 |
+
return True, "🔑 Private Key installed successfully."
|
| 48 |
+
except Exception as e:
|
| 49 |
+
return False, f"❌ Failed to install keys: {e}"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def push_to_ollama(gguf_path, ollama_repo, tag_suffix):
|
| 53 |
+
ollama_tag = f"{ollama_repo}:{tag_suffix.lower()}"
|
| 54 |
+
|
| 55 |
+
# 1. Write the Modelfile to disk
|
| 56 |
+
# The CLI needs a physical file to point to with the '-f' flag
|
| 57 |
+
modelfile_path = gguf_path.parent / "Modelfile"
|
| 58 |
+
with open(modelfile_path, "w") as f:
|
| 59 |
+
f.write(f"FROM {gguf_path.resolve()}")
|
| 60 |
+
|
| 61 |
+
logs = []
|
| 62 |
+
logs.append(format_log(f"🐳 Creating Ollama build: {ollama_tag}"))
|
| 63 |
+
|
| 64 |
+
try:
|
| 65 |
+
create_cmd = ["ollama", "create", ollama_tag, "-f", str(modelfile_path)]
|
| 66 |
+
subprocess.run(create_cmd, check=True, capture_output=True)
|
| 67 |
+
|
| 68 |
+
if modelfile_path.exists():
|
| 69 |
+
os.remove(modelfile_path)
|
| 70 |
+
|
| 71 |
+
logs.append(format_log(f"⬆️ Pushing to registry: {ollama_tag}..."))
|
| 72 |
+
|
| 73 |
+
push_cmd = ["ollama", "push", ollama_tag]
|
| 74 |
+
push_result = subprocess.run(push_cmd, capture_output=True, text=True)
|
| 75 |
+
|
| 76 |
+
if push_result.returncode == 0:
|
| 77 |
+
logs.append(format_log(f"✅ Successfully pushed {ollama_tag}"))
|
| 78 |
+
else:
|
| 79 |
+
logs.append(format_log(f"❌ Push failed: {push_result.stderr}"))
|
| 80 |
+
|
| 81 |
+
# Remove the local tag to save disk space in the container
|
| 82 |
+
subprocess.run(["ollama", "rm", ollama_tag]) # stdout=subprocess.DEVNULL
|
| 83 |
+
|
| 84 |
+
except subprocess.CalledProcessError as e:
|
| 85 |
+
# Captures errors from the 'check=True' on create_cmd
|
| 86 |
+
logs.append(format_log(f"❌ Ollama Create Error: {e}"))
|
| 87 |
+
except Exception as e:
|
| 88 |
+
logs.append(format_log(f"❌ Error on {tag_suffix}: {str(e)}"))
|
| 89 |
+
|
| 90 |
+
return logs
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def start_ollama_daemon(ollama_key):
|
| 94 |
+
print("⏳ Starting Ollama daemon in background...")
|
| 95 |
+
logs.append(format_log(f"⏳ Starting Ollama daemon in background...\n"))
|
| 96 |
+
env = os.environ.copy()
|
| 97 |
+
# Auth
|
| 98 |
+
success, auth_msg = setup_ollama_keys(ollama_key)
|
| 99 |
+
logs.append(format_log(auth_msg))
|
| 100 |
+
yield "\n".join(logs)
|
| 101 |
+
if not success:
|
| 102 |
+
logs.append(format_log("❌ Stopping: Authentication setup failed."))
|
| 103 |
+
yield "\n".join(logs)
|
| 104 |
+
return
|
| 105 |
+
process = subprocess.Popen(["ollama", "serve"], env=env) # stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
| 106 |
+
pid = process.pid
|
| 107 |
+
logs.append(format_log("⏳ Starting Ollama daemon in background..."))
|
| 108 |
+
sleep(2)
|
| 109 |
+
return pid, logs
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def stop_ollama_daemon(pid):
|
| 113 |
+
print("⏳ Stopping Ollama daemon...")
|
| 114 |
+
os.kill(pid, signal.SIGQUIT)
|
| 115 |
+
subprocess.Popen(["pkill", "ollama"]) # , stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
| 116 |
+
return logs
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def run_conversion(hf_repo, ollama_repo, hf_token, progress=gr.Progress()):
|
| 120 |
+
logs = []
|
| 121 |
+
|
| 122 |
+
work_dir = Path("conversion_work_dir")
|
| 123 |
+
download_dir = work_dir / "downloads"
|
| 124 |
+
output_dir = work_dir / "output"
|
| 125 |
+
|
| 126 |
+
if work_dir.exists():
|
| 127 |
+
shutil.rmtree(work_dir)
|
| 128 |
+
os.makedirs(download_dir, exist_ok=True)
|
| 129 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 130 |
+
|
| 131 |
+
try:
|
| 132 |
+
# Download
|
| 133 |
+
logs.append(format_log(f"⬇️ Downloading {hf_repo}..."))
|
| 134 |
+
yield "\n".join(logs)
|
| 135 |
+
|
| 136 |
+
model_path = snapshot_download(
|
| 137 |
+
repo_id=hf_repo,
|
| 138 |
+
local_dir=download_dir,
|
| 139 |
+
token=hf_token if hf_token else None
|
| 140 |
+
)
|
| 141 |
+
logs.append(format_log("✅ Download complete."))
|
| 142 |
+
yield "\n".join(logs)
|
| 143 |
+
|
| 144 |
+
# BF16
|
| 145 |
+
bf16_path = output_dir / "model-bf16.gguf"
|
| 146 |
+
logs.append(format_log("⚙️ Converting to BF16..."))
|
| 147 |
+
yield "\n".join(logs)
|
| 148 |
+
|
| 149 |
+
cmd = ["python3", str(CONVERT_SCRIPT), str(model_path), "--outtype", "bf16", "--outfile", str(bf16_path)]
|
| 150 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
| 151 |
+
|
| 152 |
+
if result.returncode == 0:
|
| 153 |
+
logs.extend(push_to_ollama(bf16_path, ollama_repo, "bf16"))
|
| 154 |
+
os.remove(bf16_path)
|
| 155 |
+
logs.append(format_log("🧹 Cleaned up BF16"))
|
| 156 |
+
else:
|
| 157 |
+
logs.append(format_log(f"⚠️ BF16 Conversion failed: {result.stderr}"))
|
| 158 |
+
yield "\n".join(logs)
|
| 159 |
+
|
| 160 |
+
# FP16
|
| 161 |
+
fp16_path = output_dir / "model-f16.gguf"
|
| 162 |
+
logs.append(format_log("⚙️ Converting to FP16 (Master)..."))
|
| 163 |
+
yield "\n".join(logs)
|
| 164 |
+
|
| 165 |
+
cmd = ["python3", str(CONVERT_SCRIPT), str(model_path), "--outtype", "f16", "--outfile", str(fp16_path)]
|
| 166 |
+
subprocess.run(cmd, check=True, capture_output=True)
|
| 167 |
+
|
| 168 |
+
logs.extend(push_to_ollama(fp16_path, ollama_repo, "f16"))
|
| 169 |
+
yield "\n".join(logs)
|
| 170 |
+
|
| 171 |
+
# Quant Loop
|
| 172 |
+
for quant in TARGET_QUANTS:
|
| 173 |
+
logs.append(format_log(f"--- {quant} ---"))
|
| 174 |
+
yield "\n".join(logs)
|
| 175 |
+
|
| 176 |
+
final_gguf = output_dir / f"model-{quant}.gguf"
|
| 177 |
+
q_cmd = [str(QUANTIZE_BIN), str(fp16_path), str(final_gguf), quant]
|
| 178 |
+
q_result = subprocess.run(q_cmd, capture_output=True, text=True)
|
| 179 |
+
|
| 180 |
+
if q_result.returncode != 0:
|
| 181 |
+
logs.append(format_log(f"❌ Quantize failed: {q_result.stderr}"))
|
| 182 |
+
continue
|
| 183 |
+
|
| 184 |
+
logs.extend(push_to_ollama(final_gguf, ollama_repo, quant))
|
| 185 |
+
os.remove(final_gguf)
|
| 186 |
+
logs.append(format_log(f"🧹 Cleaned up {quant}"))
|
| 187 |
+
yield "\n".join(logs)
|
| 188 |
+
|
| 189 |
+
if fp16_path.exists():
|
| 190 |
+
os.remove(fp16_path)
|
| 191 |
+
logs.append(format_log("🧹 Cleaned up f16"))
|
| 192 |
+
|
| 193 |
+
except Exception as e:
|
| 194 |
+
logs.append(format_log(f"❌ CRITICAL ERROR: {str(e)}"))
|
| 195 |
+
|
| 196 |
+
finally:
|
| 197 |
+
if work_dir.exists():
|
| 198 |
+
shutil.rmtree(work_dir)
|
| 199 |
+
logs.append(format_log("🏁 Job Done. Workspace cleared."))
|
| 200 |
+
yield "\n".join(logs)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def run_pipeline(hf_repo, ollama_repo, hf_token, ollama_key, progress=gr.Progress()):
|
| 204 |
+
pid = start_ollama_daemon(ollama_key)
|
| 205 |
+
# We yield from the generator
|
| 206 |
+
for update in run_conversion(hf_repo, ollama_repo, hf_token, progress):
|
| 207 |
+
yield update
|
| 208 |
+
sleep(10)
|
| 209 |
+
stop_ollama_daemon(pid)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
# --- UI ---
|
| 213 |
+
with gr.Blocks(title="HF to Ollama") as demo:
|
| 214 |
+
target_quants_str = ', '.join(str(item) for item in TARGET_QUANTS)
|
| 215 |
+
gr.Markdown("## Convert a safetensor HF repo to an Ollama repo.")
|
| 216 |
+
gr.Markdown(f"This space will generate F16, BF16, {target_quants_str} GGUFs from safetensors.")
|
| 217 |
+
gr.Markdown("And pushes them to your Ollama repo. You will need an Ollama ssh key, not an API key to push.")
|
| 218 |
+
gr.Markdown("Temporarily move yours from ~/.ollama/id_ed25519 This will cause Ollama to generate a new one")
|
| 219 |
+
gr.Markdown("After logging in set it aside, that ssh key can be used, or the old one, whichever, for spaces")
|
| 220 |
+
|
| 221 |
+
with gr.Row():
|
| 222 |
+
with gr.Column():
|
| 223 |
+
hf_input = gr.Textbox(label="Source HF Repo", placeholder="unsloth/Qwen3.5-9B", value="unsloth/Qwen3.5-0.8B")
|
| 224 |
+
hf_token_input = gr.Textbox(label="HF Token (for gated models and faster download)", type="password", value=TEST_TOKEN)
|
| 225 |
+
|
| 226 |
+
with gr.Column():
|
| 227 |
+
ollama_input = gr.Textbox(label="Destination Ollama Repo", placeholder="user/model", value="fervent_mcclintock/Qwen3.5-9B")
|
| 228 |
+
ollama_key_input = gr.Textbox(label="Ollama Private (ssh) Key", lines=7, type="password", placeholder="-----BEGIN OPENSSH PRIVATE KEY-----...", value=TEST_OKEY)
|
| 229 |
+
btn = gr.Button("Start", variant="primary")
|
| 230 |
+
|
| 231 |
+
logs = gr.TextArea(label="Logs", interactive=False, lines=10, autoscroll=True)
|
| 232 |
+
|
| 233 |
+
btn.click(
|
| 234 |
+
fn=run_pipeline,
|
| 235 |
+
inputs=[hf_input, ollama_input, hf_token_input, ollama_key_input],
|
| 236 |
+
outputs=logs
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def restart_space():
|
| 241 |
+
HfApi().restart_space(repo_id=HOST_REPO, token=HF_TOKEN, factory_reboot=True)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
scheduler = BackgroundScheduler()
|
| 245 |
+
scheduler.add_job(restart_space, "interval", seconds=21600)
|
| 246 |
+
scheduler.start()
|
| 247 |
+
|
| 248 |
+
if __name__ == "__main__":
|
| 249 |
+
demo.queue().launch(server_name="0.0.0.0", server_port=7860, theme=blurple)
|
theme.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
+
blurple = gr.themes.Glass(
|
| 4 |
+
primary_hue="purple",
|
| 5 |
+
secondary_hue=gr.themes.Color(
|
| 6 |
+
c100="#f3f4f6",
|
| 7 |
+
c200="#e5e7eb",
|
| 8 |
+
c300="#d1d5db",
|
| 9 |
+
c400="#9ca3af",
|
| 10 |
+
c50="#f9fafb",
|
| 11 |
+
c500="#6b7280",
|
| 12 |
+
c600="#4b5563",
|
| 13 |
+
c700="#374151",
|
| 14 |
+
c800="rgba(28.422987236857917, 2.1975645867375784, 39.326663208007815, 1)",
|
| 15 |
+
c900="#1c0227",
|
| 16 |
+
c950="#1c0227",
|
| 17 |
+
),
|
| 18 |
+
neutral_hue=gr.themes.Color(
|
| 19 |
+
c100="#f3e8ff",
|
| 20 |
+
c200="#e9d5ff",
|
| 21 |
+
c300="#d8b4fe",
|
| 22 |
+
c400="#c084fc",
|
| 23 |
+
c50="#faf5ff",
|
| 24 |
+
c500="#a855f7",
|
| 25 |
+
c600="rgba(83.78266724809674, 29.540070278324272, 132.9400207519531, 1)",
|
| 26 |
+
c700="rgba(48.28126126334004, 17.30792685680411, 76.3866943359375, 1)",
|
| 27 |
+
c800="rgba(46.03751121625044, 13.894996526550633, 72.53336791992187, 1)",
|
| 28 |
+
c900="#2e0e49",
|
| 29 |
+
c950="#2e0e49",
|
| 30 |
+
),
|
| 31 |
+
radius_size="none",
|
| 32 |
+
).set(
|
| 33 |
+
background_fill_primary="*neutral_700",
|
| 34 |
+
background_fill_secondary="*secondary_800",
|
| 35 |
+
border_color_accent="*neutral_600",
|
| 36 |
+
border_color_primary="*secondary_600",
|
| 37 |
+
color_accent_soft="*neutral_700",
|
| 38 |
+
link_text_color="*secondary_500",
|
| 39 |
+
link_text_color_active="*secondary_500",
|
| 40 |
+
link_text_color_hover="*secondary_400",
|
| 41 |
+
link_text_color_visited="*secondary_600",
|
| 42 |
+
code_background_fill="*neutral_800",
|
| 43 |
+
shadow_spread_dark="0px",
|
| 44 |
+
block_background_fill="*secondary_800",
|
| 45 |
+
block_border_color="*secondary_600",
|
| 46 |
+
block_border_width="1px",
|
| 47 |
+
block_label_background_fill="*secondary_700",
|
| 48 |
+
block_label_border_color="*secondary_600",
|
| 49 |
+
block_label_text_color="*neutral_200",
|
| 50 |
+
block_label_text_size="*text_sm",
|
| 51 |
+
block_title_text_color="*neutral_200",
|
| 52 |
+
checkbox_background_color="*secondary_400",
|
| 53 |
+
checkbox_border_color="*neutral_700",
|
| 54 |
+
checkbox_border_color_hover="*neutral_600",
|
| 55 |
+
checkbox_label_border_color="*secondary_700",
|
| 56 |
+
checkbox_label_gap="*form_gap_width",
|
| 57 |
+
error_background_fill="*background_fill_primary",
|
| 58 |
+
error_border_color="#ef4444",
|
| 59 |
+
error_text_color="#fef2f2",
|
| 60 |
+
error_icon_color="#ef4444",
|
| 61 |
+
input_background_fill="*secondary_600",
|
| 62 |
+
input_border_color="*secondary_600",
|
| 63 |
+
input_border_color_focus="*secondary_500",
|
| 64 |
+
input_placeholder_color="*neutral_500",
|
| 65 |
+
input_radius="*radius_xxs",
|
| 66 |
+
stat_background_fill="*primary_500",
|
| 67 |
+
table_border_color="*neutral_700",
|
| 68 |
+
table_even_background_fill="*neutral_700",
|
| 69 |
+
table_odd_background_fill="*neutral_700",
|
| 70 |
+
button_border_width="0px",
|
| 71 |
+
button_border_width_dark="0px",
|
| 72 |
+
button_transition="all 0.5s ease",
|
| 73 |
+
button_large_text_weight="500",
|
| 74 |
+
button_medium_text_weight="500",
|
| 75 |
+
button_primary_background_fill="linear-gradient(30deg, *primary_800 0%, *primary_950 50%)",
|
| 76 |
+
button_primary_background_fill_dark="linear-gradient(30deg, *primary_800 0%, *primary_950 50%)",
|
| 77 |
+
button_primary_background_fill_hover="linear-gradient(90deg, *primary_950 0%, *primary_700 60%)",
|
| 78 |
+
button_primary_background_fill_hover_dark="linear-gradient(90deg, *primary_950 0%, *primary_700 60%)",
|
| 79 |
+
button_primary_border_color="*primary_600",
|
| 80 |
+
button_primary_border_color_hover="*primary_500",
|
| 81 |
+
button_primary_text_color="white",
|
| 82 |
+
button_primary_text_color_hover="*code_background_fill",
|
| 83 |
+
button_primary_text_color_hover_dark="*code_background_fill",
|
| 84 |
+
button_primary_shadow="*button_primary_shadow",
|
| 85 |
+
button_primary_shadow_active="*button_primary_shadow",
|
| 86 |
+
button_secondary_background_fill="linear-gradient(100deg, *primary_950 0%, *primary_600 70%)",
|
| 87 |
+
button_secondary_background_fill_dark="linear-gradient(100deg, *primary_950 0%, *primary_600 70%)",
|
| 88 |
+
button_secondary_background_fill_hover="linear-gradient(90deg, *primary_700 0%, *primary_950 60%)",
|
| 89 |
+
button_secondary_background_fill_hover_dark="linear-gradient(90deg, *primary_700 0%, *primary_950 60%)",
|
| 90 |
+
button_secondary_border_color="*neutral_600",
|
| 91 |
+
button_secondary_border_color_hover="*neutral_500",
|
| 92 |
+
button_secondary_text_color="white",
|
| 93 |
+
button_secondary_text_color_hover="*table_even_background_fill",
|
| 94 |
+
button_secondary_text_color_hover_dark="*table_even_background_fill",
|
| 95 |
+
button_cancel_text_color_hover="white",
|
| 96 |
+
)
|