Instructions to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M # Run inference directly in the terminal: llama cli -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M # Run inference directly in the terminal: llama cli -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
Use Docker
docker model run hf.co/aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
- LM Studio
- Jan
- vLLM
How to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "aogavrilov/diffusiongemma-agent-iq3-cuda13" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "aogavrilov/diffusiongemma-agent-iq3-cuda13", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
- Ollama
How to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with Ollama:
ollama run hf.co/aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
- Unsloth Studio
How to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for aogavrilov/diffusiongemma-agent-iq3-cuda13 to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for aogavrilov/diffusiongemma-agent-iq3-cuda13 to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for aogavrilov/diffusiongemma-agent-iq3-cuda13 to start chatting
- Pi
How to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
Configure the model in Pi
# Install Pi: npm install -g @mariozechner/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Hermes Agent new
How to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
Run Hermes
hermes
- Atomic Chat new
- OpenClaw new
How to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
- Docker Model Runner
How to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with Docker Model Runner:
docker model run hf.co/aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
- Lemonade
How to use aogavrilov/diffusiongemma-agent-iq3-cuda13 with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull aogavrilov/diffusiongemma-agent-iq3-cuda13:Q4_K_M
Run and chat with the model
lemonade run user.diffusiongemma-agent-iq3-cuda13-Q4_K_M
List all available models
lemonade list
| #!/usr/bin/env python3 | |
| from __future__ import annotations | |
| import json | |
| import math | |
| import os | |
| import queue | |
| import subprocess | |
| import tempfile | |
| import threading | |
| import time | |
| import uuid | |
| from collections import deque | |
| from pathlib import Path | |
| from typing import Any, Generator, Iterable | |
| from fastapi import FastAPI, Header, HTTPException, Request | |
| from fastapi.responses import JSONResponse, StreamingResponse | |
| from pydantic import BaseModel, Field | |
| import uvicorn | |
| def load_dotenv(path: str | Path = ".env") -> None: | |
| p = Path(path) | |
| if not p.exists(): | |
| return | |
| for raw in p.read_text(encoding="utf-8").splitlines(): | |
| line = raw.strip() | |
| if not line or line.startswith("#") or "=" not in line: | |
| continue | |
| key, val = line.split("=", 1) | |
| key = key.strip() | |
| val = os.path.expandvars(val.strip().strip('"').strip("'")) | |
| os.environ.setdefault(key, val) | |
| load_dotenv() | |
| APP_ROOT = Path(__file__).resolve().parent | |
| DG_RUNTIME_PRESET = os.getenv("DG_RUNTIME_PRESET", "").strip().lower().replace("-", "_") | |
| MODEL_ID = os.getenv("DG_MODEL_ID", "diffusiongemma-26b-a4b-it-q4-local") | |
| API_KEY = os.getenv("DG_API_KEY", "").strip() | |
| VISUAL_SERVER_BIN = os.getenv("DG_VISUAL_SERVER_BIN", "").strip() | |
| MODEL_PATH = os.getenv("DG_MODEL", "").strip() | |
| DG_NGL_REQUESTED = int(os.getenv("DG_NGL", "18")) | |
| DG_ALLOW_FULL_NGL = int(os.getenv("DG_ALLOW_FULL_NGL", "0")) | |
| DG_MAXTOK = int(os.getenv("DG_MAXTOK", "2048")) | |
| DG_FA = int(os.getenv("DG_FA", "1")) | |
| DG_FUSED_MMQ_GLU = int(os.getenv("DG_FUSED_MMQ_GLU", "0")) | |
| DG_FUSED_MOE_DOWN_REDUCE = int(os.getenv("DG_FUSED_MOE_DOWN_REDUCE", "1")) | |
| DG_FUSED_MOE_DOWN_REDUCE_TEMP = int(os.getenv("DG_FUSED_MOE_DOWN_REDUCE_TEMP", "1")) | |
| DG_FUSED_MOE_DOWN_REDUCE_SHARED_FACTORS = int(os.getenv("DG_FUSED_MOE_DOWN_REDUCE_SHARED_FACTORS", "1")) | |
| DG_MMQ_MAX_X = os.getenv("DG_MMQ_MAX_X", "").strip() | |
| DG_MMQ_X = os.getenv("DG_MMQ_X", "").strip() | |
| DG_NO_SC_EMBT = int(os.getenv("DG_NO_SC_EMBT", "1")) | |
| DG_SC_CHUNK = int(os.getenv("DG_SC_CHUNK", "4096")) | |
| DG_SC_PRECONVERT_PROBS = int(os.getenv("DG_SC_PRECONVERT_PROBS", "1")) | |
| DG_SC_PREDEQUANT_EMB = int(os.getenv("DG_SC_PREDEQUANT_EMB", "1")) | |
| DG_DEVSAMPLE_LAZY_SAMPLE = int(os.getenv("DG_DEVSAMPLE_LAZY_SAMPLE", "1")) | |
| DG_DIFFUSION_MASK_CACHE = int(os.getenv("DG_DIFFUSION_MASK_CACHE", "1")) | |
| DG_OFFLOAD_INPUT_ENV = os.getenv("DG_OFFLOAD_INPUT") | |
| DG_LAZY_RESERVE = int(os.getenv("DG_LAZY_RESERVE", "1")) | |
| DG_VISUAL_FRAMES = int(os.getenv("DG_VISUAL_FRAMES", "0")) | |
| DG_SAFE_COOLDOWN_SECONDS = float(os.getenv("DG_SAFE_COOLDOWN_SECONDS", "30")) | |
| DG_N_BLOCKS = int(os.getenv("DG_N_BLOCKS", "2")) | |
| DG_MAX_BLOCKS = int(os.getenv("DG_MAX_BLOCKS", "8")) | |
| DG_REQUEST_TIMEOUT = int(os.getenv("DG_REQUEST_TIMEOUT", "900")) | |
| DG_STARTUP_TIMEOUT = int(os.getenv("DG_STARTUP_TIMEOUT", "900")) | |
| HOST = os.getenv("DG_HOST", "127.0.0.1") | |
| PORT = int(os.getenv("DG_PORT", "8080")) | |
| if DG_RUNTIME_PRESET in {"q4_safe", "quality", "quality_speed"}: | |
| if not MODEL_PATH: | |
| MODEL_PATH = str(APP_ROOT / "models/diffusiongemma/diffusiongemma-26B-A4B-it-Q4_K_M.gguf") | |
| if "DG_NGL" not in os.environ: | |
| DG_NGL_REQUESTED = 18 | |
| if "DG_ALLOW_FULL_NGL" not in os.environ: | |
| DG_ALLOW_FULL_NGL = 0 | |
| elif DG_RUNTIME_PRESET in {"iq3_fullgpu_fast", "fullgpu_iq3", "fullgpu_fast"}: | |
| if "DG_MODEL_ID" not in os.environ: | |
| MODEL_ID = "diffusiongemma-26b-a4b-it-iq3m-fullgpu" | |
| if not MODEL_PATH: | |
| MODEL_PATH = str(APP_ROOT / "models/diffusiongemma/diffusiongemma-26B-A4B-it-IQ3_M-from-Q4_K_M.gguf") | |
| if "DG_NGL" not in os.environ: | |
| DG_NGL_REQUESTED = 999 | |
| if "DG_ALLOW_FULL_NGL" not in os.environ: | |
| DG_ALLOW_FULL_NGL = 1 | |
| if "DG_MAXTOK" not in os.environ: | |
| DG_MAXTOK = 768 | |
| if "DG_FUSED_MMQ_GLU" not in os.environ: | |
| DG_FUSED_MMQ_GLU = 0 | |
| elif DG_RUNTIME_PRESET in {"q4_fullgpu_small", "fullgpu_q4_small"}: | |
| if "DG_MODEL_ID" not in os.environ: | |
| MODEL_ID = "diffusiongemma-26b-a4b-it-q4-fullgpu-small" | |
| if not MODEL_PATH: | |
| MODEL_PATH = str(APP_ROOT / "models/diffusiongemma/diffusiongemma-26B-A4B-it-Q4_K_M.gguf") | |
| if "DG_NGL" not in os.environ: | |
| DG_NGL_REQUESTED = 999 | |
| if "DG_ALLOW_FULL_NGL" not in os.environ: | |
| DG_ALLOW_FULL_NGL = 1 | |
| if "DG_MAXTOK" not in os.environ: | |
| DG_MAXTOK = 288 | |
| if "DG_SC_CHUNK" not in os.environ: | |
| DG_SC_CHUNK = 2048 | |
| if "DG_FUSED_MMQ_GLU" not in os.environ: | |
| DG_FUSED_MMQ_GLU = 0 | |
| DG_NGL = DG_NGL_REQUESTED if DG_ALLOW_FULL_NGL else min(DG_NGL_REQUESTED, 18) | |
| DG_OFFLOAD_INPUT = int(DG_OFFLOAD_INPUT_ENV if DG_OFFLOAD_INPUT_ENV is not None else ("1" if DG_ALLOW_FULL_NGL else "0")) | |
| class ChatMessage(BaseModel): | |
| role: str | |
| content: Any = "" | |
| class ChatCompletionRequest(BaseModel): | |
| model: str | None = None | |
| messages: list[ChatMessage] | |
| max_tokens: int | None = Field(default=None, ge=1) | |
| stream: bool = False | |
| seed: int | None = None | |
| temperature: float | None = None | |
| top_p: float | None = None | |
| n_blocks: int | None = Field(default=None, ge=1) | |
| diffusion_blocks: int | None = Field(default=None, ge=1) | |
| class DiffusionGemmaRunner: | |
| def __init__(self, bin_path: str, model_path: str, ngl: int, maxtok: int, fa: int): | |
| self.bin_path = str(Path(bin_path).expanduser()) | |
| self.model_path = str(Path(model_path).expanduser()) | |
| self.ngl = ngl | |
| self.maxtok = maxtok | |
| self.fa = fa | |
| self.proc: subprocess.Popen[str] | None = None | |
| self.stdout_q: queue.Queue[str] = queue.Queue() | |
| self.lock = threading.Lock() | |
| self.stderr_tail: deque[str] = deque(maxlen=120) | |
| self.started_at: float | None = None | |
| self.last_request_finished_at = 0.0 | |
| self._tmpdir = tempfile.TemporaryDirectory(prefix="dg-api-") | |
| def _require_paths(self) -> None: | |
| if not self.bin_path or not Path(self.bin_path).exists(): | |
| raise RuntimeError(f"DG_VISUAL_SERVER_BIN does not exist: {self.bin_path}") | |
| if not self.model_path or not Path(self.model_path).exists(): | |
| raise RuntimeError(f"DG_MODEL does not exist: {self.model_path}") | |
| def _read_stdout(self) -> None: | |
| assert self.proc is not None and self.proc.stdout is not None | |
| for line in self.proc.stdout: | |
| self.stdout_q.put(line.rstrip("\n")) | |
| def _read_stderr(self) -> None: | |
| assert self.proc is not None and self.proc.stderr is not None | |
| for line in self.proc.stderr: | |
| clean = line.rstrip("\n") | |
| self.stderr_tail.append(clean) | |
| print(f"[dg-runner] {clean}", flush=True) | |
| def start(self) -> None: | |
| self._require_paths() | |
| if self.proc and self.proc.poll() is None: | |
| return | |
| env = os.environ.copy() | |
| env.update({"NGL": str(self.ngl), "MAXTOK": str(self.maxtok), "FA": str(self.fa)}) | |
| if DG_FUSED_MMQ_GLU: | |
| env.setdefault("GGML_CUDA_MERGED_GATE_UP_GLU_NOSTREAM", "1") | |
| if DG_FUSED_MOE_DOWN_REDUCE: | |
| env.setdefault("GGML_CUDA_MOE_DOWN_REDUCE", "1") | |
| if DG_FUSED_MOE_DOWN_REDUCE_TEMP: | |
| env.setdefault("GGML_CUDA_MOE_DOWN_REDUCE_TEMP", "1") | |
| if DG_FUSED_MOE_DOWN_REDUCE_SHARED_FACTORS: | |
| env.setdefault("GGML_CUDA_MOE_DOWN_REDUCE_SHARED_FACTORS", "1") | |
| if DG_MMQ_MAX_X: | |
| env.setdefault("GGML_CUDA_MMQ_MAX_X", DG_MMQ_MAX_X) | |
| if DG_MMQ_X: | |
| env.setdefault("GGML_CUDA_MMQ_X", DG_MMQ_X) | |
| if DG_NO_SC_EMBT: | |
| env.setdefault("DG_SC_EMBT_TRANSPOSE_VIEW", "1") | |
| env.setdefault("GGML_CUDA_DG_SC_CHUNK", str(DG_SC_CHUNK)) | |
| if DG_SC_PRECONVERT_PROBS: | |
| env.setdefault("GGML_CUDA_DG_SC_PRECONVERT_PROBS", "1") | |
| if DG_SC_PREDEQUANT_EMB: | |
| env.setdefault("GGML_CUDA_DG_SC_PREDEQUANT_EMB", "1") | |
| if DG_DEVSAMPLE_LAZY_SAMPLE: | |
| env.setdefault("DG_DEVSAMPLE_LAZY_SAMPLE", "1") | |
| if DG_DIFFUSION_MASK_CACHE: | |
| env.setdefault("DG_DIFFUSION_MASK_CACHE", "1") | |
| if DG_OFFLOAD_INPUT: | |
| env.setdefault("DG_OFFLOAD_INPUT", "1") | |
| if DG_LAZY_RESERVE: | |
| env.setdefault("LLAMA_CUDA_LAZY_RESERVE", "1") | |
| env.setdefault("DG_VISUAL_FRAMES", str(DG_VISUAL_FRAMES)) | |
| cmd = [self.bin_path, self.model_path] | |
| print(f"Starting DiffusionGemma runner: {' '.join(cmd)}", flush=True) | |
| print( | |
| "Runtime env: " | |
| f"NGL={self.ngl}, MAXTOK={self.maxtok}, FA={self.fa}, " | |
| f"GGML_CUDA_MERGED_GATE_UP_GLU_NOSTREAM={env.get('GGML_CUDA_MERGED_GATE_UP_GLU_NOSTREAM', '0')}, " | |
| f"GGML_CUDA_MOE_DOWN_REDUCE={env.get('GGML_CUDA_MOE_DOWN_REDUCE', '0')}, " | |
| f"GGML_CUDA_MOE_DOWN_REDUCE_TEMP={env.get('GGML_CUDA_MOE_DOWN_REDUCE_TEMP', '0')}, " | |
| f"GGML_CUDA_MOE_DOWN_REDUCE_SHARED_FACTORS={env.get('GGML_CUDA_MOE_DOWN_REDUCE_SHARED_FACTORS', '0')}, " | |
| f"GGML_CUDA_MMQ_MAX_X={env.get('GGML_CUDA_MMQ_MAX_X', '0')}, " | |
| f"GGML_CUDA_MMQ_X={env.get('GGML_CUDA_MMQ_X', '0')}, " | |
| f"DG_SC_EMBT_TRANSPOSE_VIEW={env.get('DG_SC_EMBT_TRANSPOSE_VIEW', '0')}, " | |
| f"GGML_CUDA_DG_SC_CHUNK={env.get('GGML_CUDA_DG_SC_CHUNK', '0')}, " | |
| f"GGML_CUDA_DG_SC_PRECONVERT_PROBS={env.get('GGML_CUDA_DG_SC_PRECONVERT_PROBS', '0')}, " | |
| f"GGML_CUDA_DG_SC_PREDEQUANT_EMB={env.get('GGML_CUDA_DG_SC_PREDEQUANT_EMB', '0')}, " | |
| f"DG_DEVSAMPLE_LAZY_SAMPLE={env.get('DG_DEVSAMPLE_LAZY_SAMPLE', '0')}, " | |
| f"DG_DIFFUSION_MASK_CACHE={env.get('DG_DIFFUSION_MASK_CACHE', '0')}, " | |
| f"DG_OFFLOAD_INPUT={env.get('DG_OFFLOAD_INPUT', '0')}, " | |
| f"LLAMA_CUDA_LAZY_RESERVE={env.get('LLAMA_CUDA_LAZY_RESERVE', '0')}, " | |
| f"DG_VISUAL_FRAMES={env.get('DG_VISUAL_FRAMES', '0')}", | |
| flush=True, | |
| ) | |
| self.proc = subprocess.Popen( | |
| cmd, | |
| stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| text=True, | |
| encoding="utf-8", | |
| errors="replace", | |
| bufsize=1, | |
| env=env, | |
| ) | |
| threading.Thread(target=self._read_stdout, daemon=True).start() | |
| threading.Thread(target=self._read_stderr, daemon=True).start() | |
| self.started_at = time.time() | |
| deadline = time.monotonic() + DG_STARTUP_TIMEOUT | |
| while time.monotonic() < deadline: | |
| if self.proc.poll() is not None: | |
| tail = "\n".join(self.stderr_tail) | |
| raise RuntimeError(f"runner exited during startup with code {self.proc.returncode}\n{tail}") | |
| try: | |
| line = self.stdout_q.get(timeout=1) | |
| except queue.Empty: | |
| continue | |
| print(f"[dg-runner:stdout] {line}", flush=True) | |
| if line.startswith("READY"): | |
| return | |
| if line.startswith("ERR"): | |
| raise RuntimeError(f"runner startup error: {line}") | |
| raise TimeoutError("runner startup timed out; model may still be loading or OOM-looping") | |
| def stop(self) -> None: | |
| if not self.proc: | |
| return | |
| if self.proc.poll() is None: | |
| try: | |
| assert self.proc.stdin is not None | |
| self.proc.stdin.write("QUIT\n") | |
| self.proc.stdin.flush() | |
| self.proc.wait(timeout=10) | |
| except Exception: | |
| self.proc.terminate() | |
| try: | |
| self.proc.wait(timeout=10) | |
| except Exception: | |
| self.proc.kill() | |
| self.proc = None | |
| def _ensure_alive(self) -> None: | |
| if not self.proc or self.proc.poll() is not None: | |
| self.start() | |
| def _write_request_file(self, payload: dict[str, Any]) -> str: | |
| fd, path = tempfile.mkstemp(prefix="dg-request-", suffix=".json", dir=self._tmpdir.name, text=True) | |
| with os.fdopen(fd, "w", encoding="utf-8") as f: | |
| json.dump(payload, f, ensure_ascii=False) | |
| return path | |
| def _send_request_path(self, path: str) -> None: | |
| assert self.proc is not None and self.proc.stdin is not None | |
| self.proc.stdin.write(path + "\n") | |
| self.proc.stdin.flush() | |
| def _apply_safe_cooldown(self) -> None: | |
| if DG_SAFE_COOLDOWN_SECONDS <= 0 or self.last_request_finished_at <= 0: | |
| return | |
| wait = DG_SAFE_COOLDOWN_SECONDS - (time.monotonic() - self.last_request_finished_at) | |
| if wait > 0: | |
| print(f"[dg-safe] cooling down for {wait:.1f}s before next request", flush=True) | |
| time.sleep(wait) | |
| def _parse_committed(line: str) -> str | None: | |
| # Protocol after PR commit 7ea238c: C <block> <json-string> | |
| if not line.startswith("C "): | |
| return None | |
| parts = line.split(" ", 2) | |
| if len(parts) < 3: | |
| return "" | |
| try: | |
| return json.loads(parts[2]) | |
| except json.JSONDecodeError: | |
| return parts[2] | |
| def generate(self, messages: list[dict[str, Any]], n_blocks: int, seed: int, timeout: int) -> str: | |
| with self.lock: | |
| self._apply_safe_cooldown() | |
| self._ensure_alive() | |
| payload = {"seed": seed, "n_blocks": n_blocks, "messages": messages} | |
| path = self._write_request_file(payload) | |
| answer = "" | |
| try: | |
| self._send_request_path(path) | |
| deadline = time.monotonic() + timeout | |
| while True: | |
| if self.proc and self.proc.poll() is not None: | |
| tail = "\n".join(self.stderr_tail) | |
| raise RuntimeError(f"runner exited with code {self.proc.returncode}\n{tail}") | |
| remaining = deadline - time.monotonic() | |
| if remaining <= 0: | |
| raise TimeoutError("request timed out") | |
| try: | |
| line = self.stdout_q.get(timeout=min(1.0, remaining)) | |
| except queue.Empty: | |
| continue | |
| if line == "DONE": | |
| self.last_request_finished_at = time.monotonic() | |
| return answer | |
| if line.startswith("ERR"): | |
| raise RuntimeError(line) | |
| committed = self._parse_committed(line) | |
| if committed is not None: | |
| answer = committed | |
| finally: | |
| try: | |
| os.remove(path) | |
| except OSError: | |
| pass | |
| def stream_generate(self, messages: list[dict[str, Any]], n_blocks: int, seed: int, timeout: int) -> Generator[str, None, None]: | |
| with self.lock: | |
| self._apply_safe_cooldown() | |
| self._ensure_alive() | |
| payload = {"seed": seed, "n_blocks": n_blocks, "messages": messages} | |
| path = self._write_request_file(payload) | |
| previous = "" | |
| try: | |
| self._send_request_path(path) | |
| deadline = time.monotonic() + timeout | |
| while True: | |
| if self.proc and self.proc.poll() is not None: | |
| tail = "\n".join(self.stderr_tail) | |
| raise RuntimeError(f"runner exited with code {self.proc.returncode}\n{tail}") | |
| remaining = deadline - time.monotonic() | |
| if remaining <= 0: | |
| raise TimeoutError("request timed out") | |
| try: | |
| line = self.stdout_q.get(timeout=min(1.0, remaining)) | |
| except queue.Empty: | |
| continue | |
| if line == "DONE": | |
| self.last_request_finished_at = time.monotonic() | |
| return | |
| if line.startswith("ERR"): | |
| raise RuntimeError(line) | |
| committed = self._parse_committed(line) | |
| if committed is not None: | |
| if committed.startswith(previous): | |
| delta = committed[len(previous):] | |
| else: | |
| delta = committed | |
| previous = committed | |
| if delta: | |
| yield delta | |
| finally: | |
| try: | |
| os.remove(path) | |
| except OSError: | |
| pass | |
| runner = DiffusionGemmaRunner(VISUAL_SERVER_BIN, MODEL_PATH, DG_NGL, DG_MAXTOK, DG_FA) | |
| app = FastAPI(title="DiffusionGemma Local OpenAI-Compatible API") | |
| def check_auth(authorization: str | None) -> None: | |
| if not API_KEY: | |
| return | |
| expected = f"Bearer {API_KEY}" | |
| if authorization != expected: | |
| raise HTTPException(status_code=401, detail="Invalid or missing API key") | |
| def normalize_messages(messages: Iterable[ChatMessage]) -> list[dict[str, str]]: | |
| normalized: list[dict[str, str]] = [] | |
| for msg in messages: | |
| role = msg.role | |
| content = msg.content | |
| if isinstance(content, list): | |
| parts: list[str] = [] | |
| for item in content: | |
| if isinstance(item, dict) and item.get("type") == "text": | |
| parts.append(str(item.get("text", ""))) | |
| elif isinstance(item, str): | |
| parts.append(item) | |
| content = "\n".join(part for part in parts if part) | |
| elif content is None: | |
| content = "" | |
| else: | |
| content = str(content) | |
| normalized.append({"role": role, "content": content}) | |
| return normalized | |
| def blocks_from_request(req: ChatCompletionRequest) -> int: | |
| explicit = req.n_blocks or req.diffusion_blocks | |
| if explicit: | |
| return max(1, min(DG_MAX_BLOCKS, explicit)) | |
| if req.max_tokens: | |
| return max(1, min(DG_MAX_BLOCKS, math.ceil(req.max_tokens / 256))) | |
| return max(1, min(DG_MAX_BLOCKS, DG_N_BLOCKS)) | |
| def completion_id() -> str: | |
| return "chatcmpl-" + uuid.uuid4().hex | |
| def sse(data: dict[str, Any] | str) -> str: | |
| if data == "[DONE]": | |
| return "data: [DONE]\n\n" | |
| return "data: " + json.dumps(data, ensure_ascii=False) + "\n\n" | |
| def on_startup() -> None: | |
| runner.start() | |
| def on_shutdown() -> None: | |
| runner.stop() | |
| def healthz() -> dict[str, Any]: | |
| alive = runner.proc is not None and runner.proc.poll() is None | |
| return { | |
| "ok": alive, | |
| "model": MODEL_ID, | |
| "runtime_preset": DG_RUNTIME_PRESET, | |
| "model_path": MODEL_PATH, | |
| "ngl_requested": DG_NGL_REQUESTED, | |
| "allow_full_ngl": DG_ALLOW_FULL_NGL, | |
| "ngl": DG_NGL, | |
| "maxtok": DG_MAXTOK, | |
| "fa": DG_FA, | |
| "fused_mmq_glu": DG_FUSED_MMQ_GLU, | |
| "fused_moe_down_reduce": DG_FUSED_MOE_DOWN_REDUCE, | |
| "fused_moe_down_reduce_temp": DG_FUSED_MOE_DOWN_REDUCE_TEMP, | |
| "fused_moe_down_reduce_shared_factors": DG_FUSED_MOE_DOWN_REDUCE_SHARED_FACTORS, | |
| "mmq_max_x": DG_MMQ_MAX_X, | |
| "mmq_x": DG_MMQ_X, | |
| "no_sc_embt": DG_NO_SC_EMBT, | |
| "sc_chunk": DG_SC_CHUNK, | |
| "sc_preconvert_probs": DG_SC_PRECONVERT_PROBS, | |
| "sc_predequant_emb": DG_SC_PREDEQUANT_EMB, | |
| "devsample_lazy_sample": DG_DEVSAMPLE_LAZY_SAMPLE, | |
| "diffusion_mask_cache": DG_DIFFUSION_MASK_CACHE, | |
| "offload_input": DG_OFFLOAD_INPUT, | |
| "lazy_reserve": DG_LAZY_RESERVE, | |
| "safe_cooldown_seconds": DG_SAFE_COOLDOWN_SECONDS, | |
| "started_at": runner.started_at, | |
| "stderr_tail": list(runner.stderr_tail)[-20:], | |
| } | |
| def models(authorization: str | None = Header(default=None)) -> dict[str, Any]: | |
| check_auth(authorization) | |
| return { | |
| "object": "list", | |
| "data": [{"id": MODEL_ID, "object": "model", "created": 0, "owned_by": "local"}], | |
| } | |
| def chat_completions(req: ChatCompletionRequest, authorization: str | None = Header(default=None)) -> Any: | |
| check_auth(authorization) | |
| messages = normalize_messages(req.messages) | |
| n_blocks = blocks_from_request(req) | |
| seed = req.seed if req.seed is not None else int(time.time_ns() % (2**31 - 1)) | |
| request_timeout = DG_REQUEST_TIMEOUT | |
| cid = completion_id() | |
| created = int(time.time()) | |
| model = req.model or MODEL_ID | |
| if req.stream: | |
| def event_stream() -> Generator[str, None, None]: | |
| try: | |
| for delta in runner.stream_generate(messages, n_blocks, seed, request_timeout): | |
| yield sse({ | |
| "id": cid, | |
| "object": "chat.completion.chunk", | |
| "created": created, | |
| "model": model, | |
| "choices": [{"index": 0, "delta": {"content": delta}, "finish_reason": None}], | |
| }) | |
| yield sse({ | |
| "id": cid, | |
| "object": "chat.completion.chunk", | |
| "created": created, | |
| "model": model, | |
| "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}], | |
| }) | |
| yield sse("[DONE]") | |
| except Exception as e: | |
| yield sse({"error": {"message": str(e), "type": "runner_error"}}) | |
| yield sse("[DONE]") | |
| return StreamingResponse(event_stream(), media_type="text/event-stream") | |
| try: | |
| answer = runner.generate(messages, n_blocks, seed, request_timeout) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) from e | |
| return JSONResponse({ | |
| "id": cid, | |
| "object": "chat.completion", | |
| "created": created, | |
| "model": model, | |
| "choices": [{ | |
| "index": 0, | |
| "message": {"role": "assistant", "content": answer}, | |
| "finish_reason": "stop", | |
| }], | |
| "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}, | |
| }) | |
| async def completions(request: Request, authorization: str | None = Header(default=None)) -> Any: | |
| # Minimal legacy endpoint: wraps prompt as a user message and delegates to chat completions. | |
| check_auth(authorization) | |
| body = await request.json() | |
| prompt = body.get("prompt", "") | |
| if isinstance(prompt, list): | |
| prompt = "\n".join(map(str, prompt)) | |
| req = ChatCompletionRequest( | |
| model=body.get("model") or MODEL_ID, | |
| messages=[ChatMessage(role="user", content=str(prompt))], | |
| max_tokens=body.get("max_tokens"), | |
| stream=bool(body.get("stream", False)), | |
| seed=body.get("seed"), | |
| n_blocks=body.get("n_blocks") or body.get("diffusion_blocks"), | |
| ) | |
| return chat_completions(req, authorization=authorization) | |
| if __name__ == "__main__": | |
| uvicorn.run(app, host=HOST, port=PORT, log_level="info") | |