Both model sizes on ZeroGPU
Browse files- README.md +4 -2
- app.py +39 -29
- requirements.txt +2 -2
README.md
CHANGED
|
@@ -14,6 +14,7 @@ models:
|
|
| 14 |
- TigreGotico/jevasio-0.8b
|
| 15 |
- TigreGotico/jevasio-4b
|
| 16 |
- Qwen/Qwen3.5-0.8B-Base
|
|
|
|
| 17 |
datasets:
|
| 18 |
- TigreGotico/jevasio-decisions
|
| 19 |
tags:
|
|
@@ -28,8 +29,9 @@ Jevásio is a decision model for European Portuguese (pt-PT). It reads a state,
|
|
| 28 |
set of typed questions about it. It returns a probability for each option of each question in one forward pass. It
|
| 29 |
does not generate text.
|
| 30 |
|
| 31 |
-
This Space runs [Jevásio-
|
| 32 |
-
|
|
|
|
| 33 |
Both models are [Kev](https://github.com/jaredpalmer/kev) fine-tuned on the
|
| 34 |
[TigreGotico/jevasio-decisions](https://huggingface.co/datasets/TigreGotico/jevasio-decisions) dataset.
|
| 35 |
|
|
|
|
| 14 |
- TigreGotico/jevasio-0.8b
|
| 15 |
- TigreGotico/jevasio-4b
|
| 16 |
- Qwen/Qwen3.5-0.8B-Base
|
| 17 |
+
- Qwen/Qwen3.5-4B-Base
|
| 18 |
datasets:
|
| 19 |
- TigreGotico/jevasio-decisions
|
| 20 |
tags:
|
|
|
|
| 29 |
set of typed questions about it. It returns a probability for each option of each question in one forward pass. It
|
| 30 |
does not generate text.
|
| 31 |
|
| 32 |
+
This Space runs both sizes on ZeroGPU, in bf16: [Jevásio-4B](https://huggingface.co/TigreGotico/jevasio-4b), the more
|
| 33 |
+
accurate one, and [Jevásio-0.8B](https://huggingface.co/TigreGotico/jevasio-0.8b), the smaller one. Pick one with the
|
| 34 |
+
model selector.
|
| 35 |
Both models are [Kev](https://github.com/jaredpalmer/kev) fine-tuned on the
|
| 36 |
[TigreGotico/jevasio-decisions](https://huggingface.co/datasets/TigreGotico/jevasio-decisions) dataset.
|
| 37 |
|
app.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
-
"""Jevásio on Hugging Face Spaces (Gradio,
|
| 2 |
|
| 3 |
A state and a set of typed questions in, a probability per option out, in one forward pass. The model is loaded through
|
| 4 |
Kev's own loader (kev.checkpoint), the same path `python -m kev.serve --run TigreGotico/jevasio-0.8b` takes, and
|
| 5 |
-
requests go through kev.api and kev.model.admit exactly as /v1/systemone does.
|
| 6 |
-
|
| 7 |
"""
|
| 8 |
import html
|
| 9 |
import json
|
|
@@ -12,6 +12,7 @@ import time
|
|
| 12 |
from dataclasses import replace
|
| 13 |
|
| 14 |
import gradio as gr
|
|
|
|
| 15 |
import torch
|
| 16 |
from pydantic import ValidationError
|
| 17 |
|
|
@@ -19,22 +20,28 @@ from kev.api import SystemOneRequest, output_tokens, render, to_answers, to_reco
|
|
| 19 |
from kev.checkpoint import Checkpoint, LoadOptions
|
| 20 |
from kev.model import admit
|
| 21 |
|
| 22 |
-
|
|
|
|
| 23 |
LINK_08B = "https://huggingface.co/TigreGotico/jevasio-0.8b"
|
| 24 |
LINK_4B = "https://huggingface.co/TigreGotico/jevasio-4b"
|
| 25 |
LINK_DATA = "https://huggingface.co/datasets/TigreGotico/jevasio-decisions"
|
| 26 |
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
opts = replace(opts, dtype=torch.float32)
|
| 34 |
-
TOKENIZER, MODEL = CHECKPOINT.load("cpu", opts)
|
| 35 |
-
LOAD_SECONDS = time.perf_counter() - t0
|
| 36 |
-
print(f"[jevasio] {MODEL_REPO}: base={CHECKPOINT.meta.base}@{CHECKPOINT.meta.base_revision} dtype={MODEL.dtype} "
|
| 37 |
-
f"temperature={MODEL.head.temperature:.2f} loaded in {LOAD_SECONDS:.0f} s", flush=True)
|
| 38 |
|
| 39 |
|
| 40 |
class RequestError(ValueError):
|
|
@@ -50,19 +57,22 @@ def parse_request(payload):
|
|
| 50 |
raise RequestError(f"Pedido inválido em `{where}`: {first.get('msg')}") from None
|
| 51 |
|
| 52 |
|
| 53 |
-
def systemone(req):
|
| 54 |
"""The /v1/systemone response body kev.serve returns for this request, plus the latency of the forward pass."""
|
|
|
|
|
|
|
|
|
|
| 55 |
rec, meta = to_record(req)
|
| 56 |
try:
|
| 57 |
-
enc = admit(
|
| 58 |
except ValueError as e:
|
| 59 |
raise RequestError(str(e)) from None
|
| 60 |
start = time.perf_counter()
|
| 61 |
-
probs =
|
| 62 |
ms = (time.perf_counter() - start) * 1000
|
| 63 |
answers = to_answers([p.tolist() for p in probs], meta)
|
| 64 |
-
return {"model":
|
| 65 |
-
"usage": {"input_tokens": len(enc["ids"]), "output_tokens": output_tokens(
|
| 66 |
"latency_ms": round(ms, 1)}
|
| 67 |
|
| 68 |
|
|
@@ -91,7 +101,7 @@ def render_answers(req, resp):
|
|
| 91 |
usage = resp["usage"]
|
| 92 |
parts = ['<div style="font-size:12px;color:var(--body-text-color-subdued);margin:6px 0 10px">'
|
| 93 |
f'{len(resp["answers"])} pergunta(s) · {usage["input_tokens"]} tokens de entrada · '
|
| 94 |
-
f'{resp["latency_ms"]:.0f} ms
|
| 95 |
for qid, a in resp["answers"].items():
|
| 96 |
instructions = render(req.questions[qid].instructions)
|
| 97 |
if a["type"] == "noul":
|
|
@@ -167,18 +177,18 @@ def build_request(state, choice_id, choice_instr, choice_opts, noul_id, noul_ins
|
|
| 167 |
return {"state": parse_state(state), "model": "kev-latest", "questions": questions}
|
| 168 |
|
| 169 |
|
| 170 |
-
def decide(state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels):
|
| 171 |
"""Answer the builder's questions about the state. Returns the rendered answers, the request and the response."""
|
| 172 |
try:
|
| 173 |
payload = build_request(state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels)
|
| 174 |
req = parse_request(payload)
|
| 175 |
-
resp = systemone(req)
|
| 176 |
except RequestError as e:
|
| 177 |
return error_html(str(e)), {}, {"error": str(e)}
|
| 178 |
return render_answers(req, resp), payload, resp
|
| 179 |
|
| 180 |
|
| 181 |
-
def decide_raw(request_json):
|
| 182 |
"""Answer a raw System One request (JSON text). Returns a status line and the response or an error object."""
|
| 183 |
try:
|
| 184 |
payload = json.loads(request_json or "")
|
|
@@ -189,7 +199,7 @@ def decide_raw(request_json):
|
|
| 189 |
message = 'O pedido tem de ser um objeto JSON com "state" e "questions".'
|
| 190 |
return f"**Erro:** {message}", {"error": message}
|
| 191 |
try:
|
| 192 |
-
resp = systemone(parse_request(payload))
|
| 193 |
except RequestError as e:
|
| 194 |
return f"**Erro:** {e}", {"error": str(e)}
|
| 195 |
return f"Respondido em {resp['latency_ms']:.0f} ms.", resp
|
|
@@ -263,8 +273,7 @@ INTRO = f"""# Jevásio
|
|
| 263 |
Modelo de decisão para português europeu. Recebe um **estado** (um texto ou um objeto JSON) e perguntas tipadas, e
|
| 264 |
devolve uma probabilidade para cada opção de cada pergunta, numa só passagem pelo modelo. Não gera texto.
|
| 265 |
|
| 266 |
-
|
| 267 |
-
[Jevásio-4B]({LINK_4B}), mais exato, que precisa de GPU.
|
| 268 |
"""
|
| 269 |
|
| 270 |
ABOUT = f"""Há três tipos de pergunta: `choice` (escolher uma opção com nome), `noul` (sim ou não) e `score` (um nível numa
|
|
@@ -273,7 +282,7 @@ valores em português, como nos dados de treino.
|
|
| 273 |
|
| 274 |
O Jevásio é o [Kev](https://github.com/jaredpalmer/kev) afinado com o conjunto
|
| 275 |
[TigreGotico/jevasio-decisions]({LINK_DATA}). A probabilidade usa a temperatura de calibração guardada no modelo
|
| 276 |
-
(T = {
|
| 277 |
|
| 278 |
Licença CC BY-NC-SA 4.0: só para investigação não comercial. Não use o modelo em decisões com consequências legais,
|
| 279 |
médicas, financeiras ou laborais sem revisão humana.
|
|
@@ -293,6 +302,7 @@ first = EXAMPLES[0]
|
|
| 293 |
with gr.Blocks(title="Jevásio") as demo:
|
| 294 |
with gr.Column(elem_id="col-container"):
|
| 295 |
gr.Markdown(INTRO)
|
|
|
|
| 296 |
with gr.Tabs():
|
| 297 |
with gr.Tab("Perguntas"):
|
| 298 |
with gr.Row():
|
|
@@ -330,7 +340,7 @@ with gr.Blocks(title="Jevásio") as demo:
|
|
| 330 |
inputs = [state_box, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels]
|
| 331 |
gr.Examples(examples=[example_row(ex) for ex in EXAMPLES], example_labels=[ex["name"] for ex in EXAMPLES],
|
| 332 |
inputs=inputs, label="Exemplos")
|
| 333 |
-
run_btn.click(fn=decide, inputs=inputs, outputs=[answers_html, request_json, response_json], api_name="decide")
|
| 334 |
|
| 335 |
with gr.Tab("Avançado"):
|
| 336 |
gr.Markdown("Cole um pedido em JSON no formato System One (`POST /v1/systemone`) e veja a resposta em JSON.")
|
|
@@ -341,7 +351,7 @@ with gr.Blocks(title="Jevásio") as demo:
|
|
| 341 |
with gr.Column(scale=1):
|
| 342 |
raw_status = gr.Markdown()
|
| 343 |
raw_out = gr.JSON(label="Resposta (JSON)")
|
| 344 |
-
raw_btn.click(fn=decide_raw, inputs=[raw_in], outputs=[raw_status, raw_out], api_name="systemone")
|
| 345 |
|
| 346 |
with gr.Tab("Sobre"):
|
| 347 |
gr.Markdown(ABOUT)
|
|
|
|
| 1 |
+
"""Jevásio on Hugging Face Spaces (Gradio, ZeroGPU).
|
| 2 |
|
| 3 |
A state and a set of typed questions in, a probability per option out, in one forward pass. The model is loaded through
|
| 4 |
Kev's own loader (kev.checkpoint), the same path `python -m kev.serve --run TigreGotico/jevasio-0.8b` takes, and
|
| 5 |
+
requests go through kev.api and kev.model.admit exactly as /v1/systemone does. Both sizes load in bf16 at start-up, and
|
| 6 |
+
each forward pass runs inside @spaces.GPU, so a request holds a GPU only for that pass.
|
| 7 |
"""
|
| 8 |
import html
|
| 9 |
import json
|
|
|
|
| 12 |
from dataclasses import replace
|
| 13 |
|
| 14 |
import gradio as gr
|
| 15 |
+
import spaces
|
| 16 |
import torch
|
| 17 |
from pydantic import ValidationError
|
| 18 |
|
|
|
|
| 20 |
from kev.checkpoint import Checkpoint, LoadOptions
|
| 21 |
from kev.model import admit
|
| 22 |
|
| 23 |
+
MODELS = {"Jevásio-4B": "TigreGotico/jevasio-4b", "Jevásio-0.8B": "TigreGotico/jevasio-0.8b"}
|
| 24 |
+
DEFAULT = "Jevásio-4B"
|
| 25 |
LINK_08B = "https://huggingface.co/TigreGotico/jevasio-0.8b"
|
| 26 |
LINK_4B = "https://huggingface.co/TigreGotico/jevasio-4b"
|
| 27 |
LINK_DATA = "https://huggingface.co/datasets/TigreGotico/jevasio-decisions"
|
| 28 |
|
| 29 |
+
LOADED = {}
|
| 30 |
+
for name, repo in MODELS.items():
|
| 31 |
+
t0 = time.perf_counter()
|
| 32 |
+
checkpoint = Checkpoint(repo)
|
| 33 |
+
opts = LoadOptions.from_env()
|
| 34 |
+
if opts.dtype is None:
|
| 35 |
+
opts = replace(opts, dtype=torch.bfloat16)
|
| 36 |
+
LOADED[name] = checkpoint.load("cuda", opts)
|
| 37 |
+
tok, model = LOADED[name]
|
| 38 |
+
print(f"[jevasio] {repo}: base={checkpoint.meta.base}@{checkpoint.meta.base_revision} dtype={model.dtype} "
|
| 39 |
+
f"temperature={model.head.temperature:.2f} loaded in {time.perf_counter() - t0:.0f} s", flush=True)
|
| 40 |
|
| 41 |
+
|
| 42 |
+
@spaces.GPU(duration=30)
|
| 43 |
+
def forward(name, enc):
|
| 44 |
+
return [p.float().cpu() for p in LOADED[name][1].probs(enc)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
class RequestError(ValueError):
|
|
|
|
| 57 |
raise RequestError(f"Pedido inválido em `{where}`: {first.get('msg')}") from None
|
| 58 |
|
| 59 |
|
| 60 |
+
def systemone(req, name=DEFAULT):
|
| 61 |
"""The /v1/systemone response body kev.serve returns for this request, plus the latency of the forward pass."""
|
| 62 |
+
if name not in LOADED:
|
| 63 |
+
raise RequestError(f"Modelo desconhecido: {name}. Opções: {', '.join(MODELS)}.")
|
| 64 |
+
tokenizer, model = LOADED[name]
|
| 65 |
rec, meta = to_record(req)
|
| 66 |
try:
|
| 67 |
+
enc = admit(model, tokenizer, rec)
|
| 68 |
except ValueError as e:
|
| 69 |
raise RequestError(str(e)) from None
|
| 70 |
start = time.perf_counter()
|
| 71 |
+
probs = forward(name, enc)
|
| 72 |
ms = (time.perf_counter() - start) * 1000
|
| 73 |
answers = to_answers([p.tolist() for p in probs], meta)
|
| 74 |
+
return {"model": MODELS[name], "answers": answers,
|
| 75 |
+
"usage": {"input_tokens": len(enc["ids"]), "output_tokens": output_tokens(tokenizer, answers)},
|
| 76 |
"latency_ms": round(ms, 1)}
|
| 77 |
|
| 78 |
|
|
|
|
| 101 |
usage = resp["usage"]
|
| 102 |
parts = ['<div style="font-size:12px;color:var(--body-text-color-subdued);margin:6px 0 10px">'
|
| 103 |
f'{len(resp["answers"])} pergunta(s) · {usage["input_tokens"]} tokens de entrada · '
|
| 104 |
+
f'{resp["model"].split("/")[1]} · {resp["latency_ms"]:.0f} ms · nenhum texto gerado</div>']
|
| 105 |
for qid, a in resp["answers"].items():
|
| 106 |
instructions = render(req.questions[qid].instructions)
|
| 107 |
if a["type"] == "noul":
|
|
|
|
| 177 |
return {"state": parse_state(state), "model": "kev-latest", "questions": questions}
|
| 178 |
|
| 179 |
|
| 180 |
+
def decide(name, state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels):
|
| 181 |
"""Answer the builder's questions about the state. Returns the rendered answers, the request and the response."""
|
| 182 |
try:
|
| 183 |
payload = build_request(state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels)
|
| 184 |
req = parse_request(payload)
|
| 185 |
+
resp = systemone(req, name)
|
| 186 |
except RequestError as e:
|
| 187 |
return error_html(str(e)), {}, {"error": str(e)}
|
| 188 |
return render_answers(req, resp), payload, resp
|
| 189 |
|
| 190 |
|
| 191 |
+
def decide_raw(name, request_json):
|
| 192 |
"""Answer a raw System One request (JSON text). Returns a status line and the response or an error object."""
|
| 193 |
try:
|
| 194 |
payload = json.loads(request_json or "")
|
|
|
|
| 199 |
message = 'O pedido tem de ser um objeto JSON com "state" e "questions".'
|
| 200 |
return f"**Erro:** {message}", {"error": message}
|
| 201 |
try:
|
| 202 |
+
resp = systemone(parse_request(payload), name)
|
| 203 |
except RequestError as e:
|
| 204 |
return f"**Erro:** {e}", {"error": str(e)}
|
| 205 |
return f"Respondido em {resp['latency_ms']:.0f} ms.", resp
|
|
|
|
| 273 |
Modelo de decisão para português europeu. Recebe um **estado** (um texto ou um objeto JSON) e perguntas tipadas, e
|
| 274 |
devolve uma probabilidade para cada opção de cada pergunta, numa só passagem pelo modelo. Não gera texto.
|
| 275 |
|
| 276 |
+
Escolha o modelo: o [Jevásio-4B]({LINK_4B}) é mais exato, o [Jevásio-0.8B]({LINK_08B}) é mais pequeno e rápido.
|
|
|
|
| 277 |
"""
|
| 278 |
|
| 279 |
ABOUT = f"""Há três tipos de pergunta: `choice` (escolher uma opção com nome), `noul` (sim ou não) e `score` (um nível numa
|
|
|
|
| 282 |
|
| 283 |
O Jevásio é o [Kev](https://github.com/jaredpalmer/kev) afinado com o conjunto
|
| 284 |
[TigreGotico/jevasio-decisions]({LINK_DATA}). A probabilidade usa a temperatura de calibração guardada no modelo
|
| 285 |
+
(T = {LOADED["Jevásio-4B"][1].head.temperature:.2f} no 4B e {LOADED["Jevásio-0.8B"][1].head.temperature:.2f} no 0.8B). Antes de definir um limiar, meça o modelo com os seus próprios dados.
|
| 286 |
|
| 287 |
Licença CC BY-NC-SA 4.0: só para investigação não comercial. Não use o modelo em decisões com consequências legais,
|
| 288 |
médicas, financeiras ou laborais sem revisão humana.
|
|
|
|
| 302 |
with gr.Blocks(title="Jevásio") as demo:
|
| 303 |
with gr.Column(elem_id="col-container"):
|
| 304 |
gr.Markdown(INTRO)
|
| 305 |
+
model_choice = gr.Radio(list(MODELS), value=DEFAULT, label="Modelo")
|
| 306 |
with gr.Tabs():
|
| 307 |
with gr.Tab("Perguntas"):
|
| 308 |
with gr.Row():
|
|
|
|
| 340 |
inputs = [state_box, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels]
|
| 341 |
gr.Examples(examples=[example_row(ex) for ex in EXAMPLES], example_labels=[ex["name"] for ex in EXAMPLES],
|
| 342 |
inputs=inputs, label="Exemplos")
|
| 343 |
+
run_btn.click(fn=decide, inputs=[model_choice, *inputs], outputs=[answers_html, request_json, response_json], api_name="decide")
|
| 344 |
|
| 345 |
with gr.Tab("Avançado"):
|
| 346 |
gr.Markdown("Cole um pedido em JSON no formato System One (`POST /v1/systemone`) e veja a resposta em JSON.")
|
|
|
|
| 351 |
with gr.Column(scale=1):
|
| 352 |
raw_status = gr.Markdown()
|
| 353 |
raw_out = gr.JSON(label="Resposta (JSON)")
|
| 354 |
+
raw_btn.click(fn=decide_raw, inputs=[model_choice, raw_in], outputs=[raw_status, raw_out], api_name="systemone")
|
| 355 |
|
| 356 |
with gr.Tab("Sobre"):
|
| 357 |
gr.Markdown(ABOUT)
|
requirements.txt
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
kev @ git+https://github.com/jaredpalmer/kev@1f04cfa28e6a708485b28a9585010b9e436b776e
|
|
|
|
| 1 |
+
torch==2.8.0
|
| 2 |
+
spaces
|
| 3 |
kev @ git+https://github.com/jaredpalmer/kev@1f04cfa28e6a708485b28a9585010b9e436b776e
|