neuronal-gpu / app.py
amogaddy's picture
Neuronal: modello su ZeroGPU
e8a2ef5 verified
Raw History Blame Contribute Delete
6.35 kB
"""Neuronal su ZeroGPU: qui gira SOLO il modello.
La pagina, gli strumenti, la voce, i ricordi e le sessioni stanno nello Space col container
(amogaddy/neuronal). Quando quello deve calcolare chiama `sessione`: qui parte il modello sulla GPU,
si collega al container, fa i calcoli che gli vengono chiesti e dopo pochi secondi restituisce la GPU.
All'accensione, sulla CPU (non consuma tempo di GPU): scarica llama.cpp e il modello, compila neuronal-brain."""
import asyncio, glob, hmac, os, re, subprocess, tarfile, time, urllib.request
import gradio as gr
import spaces
from huggingface_hub import hf_hub_download
from ponte import ponte
QUI = os.path.dirname(os.path.abspath(__file__))
BASE = "/tmp/neuronal"
LLAMA = "https://github.com/ggml-org/llama.cpp/releases/download/b11200/"
PACCHETTI = ["llama-b11200-bin-ubuntu-cuda-12.8-x64.tar.gz", "cudart-llama-b11200-bin-ubuntu-cuda-12.8-x64.tar.gz"]
# llama.cpp è compilato per una libreria di sistema più nuova di quella dello Space: se ne affianca una copia
UBUNTU = "http://archive.ubuntu.com/ubuntu/pool/main/g/"
DEB = [("glibc/", r"libc6_2\.39-0ubuntu8[.\d]*_amd64\.deb"), ("gcc-14/", r"libstdc\+\+6_14[^\"]*24\.04[.\d]*_amd64\.deb"),
("gcc-14/", r"libgcc-s1_14[^\"]*24\.04[.\d]*_amd64\.deb"), ("gcc-14/", r"libgomp1_14[^\"]*24\.04[.\d]*_amd64\.deb")]
CHIAVE = os.environ.get("NEURONAL_CHIAVE", "")
MASSIMO = 110 # durata massima di una sessione di GPU, in secondi
REGISTRO = os.path.join(BASE, "sessioni.log")
def prepara():
os.makedirs(BASE, exist_ok=True)
t = time.time()
for nome in PACCHETTI:
fatto = os.path.join(BASE, nome + ".ok")
if not os.path.exists(fatto):
arch = os.path.join(BASE, nome)
urllib.request.urlretrieve(LLAMA + nome, arch)
with tarfile.open(arch) as tar:
tar.extractall(os.path.join(BASE, "llama"))
os.remove(arch)
open(fatto, "w").close()
libs = sorted({d for d, _, fs in os.walk(os.path.join(BASE, "llama")) if any(".so" in f for f in fs)})
llama = next(d for d in libs if os.path.exists(os.path.join(d, "libllama.so")))
sistema = os.path.join(BASE, "glibc")
if not os.path.exists(os.path.join(sistema, ".ok")):
for cartella, modello in DEB:
elenco = urllib.request.urlopen(UBUNTU + cartella, timeout=60).read().decode()
nome = sorted(set(re.findall(modello, elenco)))[-1]
deb = os.path.join(BASE, nome)
urllib.request.urlretrieve(UBUNTU + cartella + nome, deb)
subprocess.run(["dpkg-deb", "-x", deb, sistema], check=True)
os.remove(deb)
open(os.path.join(sistema, ".ok"), "w").close()
sis = os.path.join(sistema, "usr", "lib", "x86_64-linux-gnu")
if not os.path.exists(os.path.join(sis, "libc.so.6")):
sis = os.path.join(sistema, "lib", "x86_64-linux-gnu")
cervello = os.path.join(BASE, "neuronal-brain")
src = os.path.join(QUI, "src")
subprocess.run(["gcc", "-O2", "-std=gnu11", "-o", cervello, os.path.join(src, "brain_worker.c"), os.path.join(src, "brain.c"),
"-I" + src, "-I" + os.path.join(src, "llama"), "-L" + llama, "-lmtmd", "-lllama", "-lggml", "-lggml-base",
"-lm", "-lpthread", "-Wl,--allow-shlib-undefined"], check=True)
modello = hf_hub_download("prism-ml/Bonsai-27B-gguf", "Bonsai-27B-Q1_0.gguf")
visione = hf_hub_download("prism-ml/Bonsai-27B-gguf", "Bonsai-27B-mmproj-Q8_0.gguf")
print(f"Pronto in {time.time() - t:.0f} s: llama.cpp, modello e neuronal-brain.", flush=True)
percorsi = os.pathsep.join([sis] + libs + ["/usr/lib/x86_64-linux-gnu"])
return [os.path.join(sis, "ld-linux-x86-64.so.2"), "--library-path", percorsi, cervello, modello, llama, visione, "32768"]
COMANDO = prepara()
def valida(chiave):
return bool(CHIAVE) and hmac.compare_digest(str(chiave), CHIAVE)
def limita(secondi):
try:
return max(10, min(int(secondi), MASSIMO))
except (TypeError, ValueError):
return 10
def durata(chiave, url, secondi):
"""Quanta GPU chiedere: il tempo della sessione più il margine per chiudere. ZeroGPU concede la sessione
solo se nel tempo del giorno ne resta almeno altrettanto, ma conta solo quello usato davvero."""
return limita(secondi) + 8
@spaces.GPU(duration=durata)
def sessione(chiave, url, secondi):
"""Avvia il modello sulla GPU e lo collega al motore di Neuronal che l'ha chiesto."""
if not valida(chiave):
return "chiave sbagliata"
if not re.fullmatch(r"wss?://[A-Za-z0-9.\-:]+/ws\?role=brain&ticket=[0-9a-f]{16,40}", str(url)):
return "indirizzo non valido"
secondi = limita(secondi)
with open(REGISTRO, "ab") as reg:
reg.write(f"\n--- {time.strftime('%H:%M:%S')} sessione di {secondi} s\n".encode())
reg.flush()
try:
usati = asyncio.run(ponte(COMANDO, url, secondi, stderr=reg))
except Exception as e:
reg.write(f"ponte: {type(e).__name__}: {e}\n".encode())
return f"collegamento al motore non riuscito ({type(e).__name__})"
return f"ok {usati:.1f}"
def diagnosi(chiave):
"""Le ultime righe scritte dal modello (per capire un problema senza usare la GPU)."""
if not valida(chiave):
return "chiave sbagliata"
try:
return open(REGISTRO, encoding="utf-8", errors="replace").read()[-6000:]
except OSError:
return "nessuna sessione finora"
with gr.Blocks(title="Neuronal GPU") as pagina:
gr.Markdown("# 🧠 Neuronal — il modello\nQui gira solo il modello di Neuronal, sulla GPU. "
"Per usarlo vai su **[amogaddy/neuronal](https://huggingface.co/spaces/amogaddy/neuronal)**: "
"la pagina, gli strumenti e la voce sono lì.\n\nMade in Italy 🇮🇹")
with gr.Accordion("Servizio (lo usa il motore di Neuronal)", open=False):
chiave = gr.Textbox(label="Chiave", type="password")
url = gr.Textbox(label="Motore")
secondi = gr.Number(label="Secondi", value=50)
esito = gr.Textbox(label="Esito", lines=12)
gr.Button("Sessione").click(sessione, [chiave, url, secondi], esito, api_name="sessione")
gr.Button("Diagnosi").click(diagnosi, [chiave], esito, api_name="diagnosi")
pagina.launch(show_error=True)