#!/usr/bin/env python3 """ Phox on Hugging Face Spaces — BQSM Wave-Rider Inference Engine. This app wraps the Phoenix Brain C inference binary in a Gradio interface. At Space startup it: 1. Downloads the tokenizer from HF Hub (tokenizers lib, no PyTorch) 2. On button click: downloads pre-built phoenix binary + model from HF Hub 3. Starts phoenix in --chat mode with shared-memory ring buffers 4. Provides a chat UI via Gradio The phoenix binary runs on CPU (AVX2) — the wave-rider physics engine doesn't use PyTorch, so ZeroGPU doesn't accelerate it directly. But the Space provides a free public endpoint to chat with Phox. """ import os import sys import time import json import mmap import struct import subprocess import threading import gradio as gr from huggingface_hub import hf_hub_download # ── Config ── SPACE_DIR = os.path.dirname(os.path.abspath(__file__)) MODEL_REPO = "compunerd/emerging-systems-models" MODEL_FILENAME = "gemma4-12b-ternary-normed.bqsm" MODEL_SUBDIR = "/tmp/phoenix_models" MODEL_PATH = os.path.join(MODEL_SUBDIR, MODEL_FILENAME) STATE_FILE = "/tmp/phoenix_state.jsonl" STATE_LOG = "/tmp/phoenix_daemon.log" RING_IN_PATH = "/tmp/phoenix_ring_in" RING_OUT_PATH = "/tmp/phoenix_ring_out" RING_CAPACITY = 4096 RING_BUF_SIZE = RING_CAPACITY * 4 + 16 PHOENIX_BIN = "/tmp/phoenix" MIXER_BIN = "/tmp/mixer" CHAT_TIMEOUT_S = 120 SENTINEL_QUIT = 0xFFFFFFFE # ── Global state ── _phoenix_proc = None _mixer_proc = None _ring_in_mm = None _ring_out_mm = None _ring_in_fd = None _ring_out_fd = None _state_lock = threading.Lock() _chat_messages = [] _boot_status = {"ready": False, "phase": "idle", "message": "Not started"} _boot_thread = None _tokenizer = None def _update_boot(phase, message, ready=False): """Update boot status (thread-safe).""" with _state_lock: _boot_status["phase"] = phase _boot_status["message"] = message _boot_status["ready"] = ready def load_tokenizer(): """Download and load the Gemma 4 tokenizer from HF Hub.""" global _tokenizer print("[Phox Space] Loading Gemma 4 tokenizer from Hub...") try: tok_path = hf_hub_download( repo_id=MODEL_REPO, filename="tokenizer/tokenizer.json") from tokenizers import Tokenizer _tokenizer = Tokenizer.from_file(tok_path) # Build a simple vocab-size attribute for compatibility _tokenizer.vocab_size = len(_tokenizer.get_vocab()) print(f"[Phox Space] Tokenizer loaded (vocab={_tokenizer.vocab_size})") except Exception as e: print(f"[Phox Space] Tokenizer download failed: {e}") # Fallback: try local paths for tok_path in [ os.path.join(MODEL_SUBDIR, "tokenizer", "tokenizer.json"), "/home/compunerd/models/gemma4-tokenizer/tokenizer.json", ]: try: from tokenizers import Tokenizer _tokenizer = Tokenizer.from_file(tok_path) _tokenizer.vocab_size = len(_tokenizer.get_vocab()) print(f"[Phox Space] Tokenizer loaded from {tok_path}") break except Exception: pass if _tokenizer is None: print("[Phox Space] WARNING: No tokenizer available — chat will not work") def boot_phoenix(): """Full startup sequence: download model, build binary, start phoenix.""" # Phase 0: Tokenizer _update_boot("tokenizer", "Loading tokenizer from Hub...") load_tokenizer() # Phase 1: Download model _update_boot("download", "Downloading BQSM model from HF Hub...") try: os.makedirs(MODEL_SUBDIR, exist_ok=True) if not os.path.exists(MODEL_PATH): hf_hub_download( repo_id=MODEL_REPO, filename=MODEL_FILENAME, local_dir=MODEL_SUBDIR, local_dir_use_symlinks=False, ) _update_boot("download", f"Model ready: {os.path.getsize(MODEL_PATH)} bytes") except Exception as e: _update_boot("download", f"Model download FAILED: {e}") return # Phase 2: Download pre-built binary from HF Hub (avoids compilation on Space) _update_boot("build", "Checking for phoenix binary...") # Only download if binary doesn't exist (cached after first download) if not os.path.exists(PHOENIX_BIN): try: _update_boot("build", "Downloading phoenix binary from Hub...") hf_hub_download( repo_id=MODEL_REPO, filename="phoenix", local_dir="/tmp", local_dir_use_symlinks=False, force_filename="phoenix", ) os.chmod(PHOENIX_BIN, 0o755) except Exception as e: # Fallback: compile from source _update_boot("build", f"Binary download failed, compiling... ({e})") src_path = os.path.join(SPACE_DIR, "phoenix_brain.c") if os.path.exists(src_path): cc = os.environ.get("CC", "cc") cmd = [cc, "-O3", "-std=c11", "-march=native", "-fopenmp", src_path, "-o", PHOENIX_BIN, "-lm"] result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode != 0: _update_boot("build", f"Build FAILED: {result.stderr[:500]}") return else: _update_boot("build", "No binary or source found!") return _update_boot("build", "Binary ready") # Phase 3: Start phoenix _update_boot("start", "Starting phoenix in chat mode...") global _phoenix_proc with _state_lock: if _phoenix_proc and _phoenix_proc.poll() is None: _update_boot("start", "Already running", ready=True) return init_ring_buffers() _phoenix_proc = subprocess.Popen( [PHOENIX_BIN, MODEL_PATH, "--chat"], stdout=open(STATE_LOG, 'a'), stderr=subprocess.STDOUT, ) _update_boot("start", f"Phoenix PID {_phoenix_proc.pid} — loading model (~40s)...") # Start a monitor thread to check when phoenix enters chat mode threading.Thread(target=_monitor_phoenix, daemon=True).start() def _monitor_phoenix(): """Background thread: watches daemon log for 'Chat Mode', then sets ready.""" import time as _time for _ in range(120): # Check for up to 2 minutes _time.sleep(0.5) try: size = os.path.getsize(STATE_LOG) with open(STATE_LOG, 'rb') as f: f.seek(max(0, size - 5000)) log = f.read().decode('utf-8', errors='replace') if "Chat Mode" in log or "ring buffer" in log.lower() or "ready" in log.lower(): _update_boot("ready", "Phoenix is ready to chat!") return # Check if process died with _state_lock: if _phoenix_proc and _phoenix_proc.poll() is not None: _update_boot("error", f"Phoenix exited (code {_phoenix_proc.returncode})") return except (FileNotFoundError, PermissionError): pass _update_boot("ready", "Phoenix boot timed out — may still be initializing.") def init_ring_buffers(): """Create ring buffer files with initialized headers.""" for path in [RING_IN_PATH, RING_OUT_PATH]: try: os.unlink(path) except FileNotFoundError: pass fd = os.open(path, os.O_CREAT | os.O_RDWR, 0o666) os.write(fd, b'\x00' * RING_BUF_SIZE) os.lseek(fd, 0, 0) os.write(fd, struct.pack('