Spaces:
Running on Zero
Running on Zero
File size: 13,501 Bytes
f8b6739 f145385 f8b6739 15c70fa f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 698a46b f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 698a46b f8b6739 f145385 f8b6739 698a46b f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 f145385 f8b6739 698a46b f8b6739 698a46b f8b6739 f145385 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 | import os
import re
import time
from threading import Thread
from typing import Any, Dict, Generator, List, Tuple
import gradio as gr
import spaces
import torch
from safetensors.torch import load_file
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
from nanogentzen.kernel import Sequent, verify_proof_tree
from nanogentzen.model import GentzenPolicyValueNet, PolicyValueConfig
from nanogentzen.parser import parse_natural_language, parse_symbolic_sequent
from nanogentzen.search import NeuralProofSearch
from nanogentzen.tokenizer import LogicTokenizer
# =====================================================================
# 1. LOAD LOCAL LLM (SYSTEM 1)
# =====================================================================
MODEL_ID = "Qwen/Qwen3.5-4B"
print(f"[*] Loading System 1 LLM: {MODEL_ID}...")
llm_tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
llm_model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
torch_dtype=torch.bfloat16,
device_map="auto",
)
llm_model.eval()
print("[+] System 1 loaded successfully.")
# =====================================================================
# 2. LOAD NANOGENTZEN KERNEL (SYSTEM 2)
# =====================================================================
def render_tree(node: dict, indent: int = 0) -> str:
space = " " * indent
rule = node.get("rule", "UNKNOWN")
seq = node.get("sequent", "")
res = f"{space}β’ [{rule}] {seq}\n"
for child in node.get("branches", []):
res += render_tree(child, indent + 1)
return res
def load_gentzen_engine():
device = "cpu"
tokenizer = LogicTokenizer()
config = PolicyValueConfig(vocab_size=tokenizer.vocab_size)
weights_file = (
"nanogentzen_model.safetensors"
if os.path.exists("nanogentzen_model.safetensors")
else "model.safetensors"
)
if not os.path.exists(weights_file):
raise FileNotFoundError(f"Missing weights file: {weights_file}")
model = GentzenPolicyValueNet(config).to(device)
model.load_state_dict(load_file(weights_file, device=device))
model.eval()
return NeuralProofSearch(model, tokenizer, device=device)
searcher = load_gentzen_engine()
def prove_comprehensive(query_str: str, max_depth: int = 8) -> Dict[str, Any]:
seq = parse_symbolic_sequent(query_str)
desc = "Formal symbolic sequent"
if seq is None:
nl_res = parse_natural_language(query_str)
if nl_res:
seq, desc = nl_res
if seq is None:
return {"success": False, "error": "Unable to parse into a valid sequent Ξ β’ Ξ."}
# 1. Intuitionistic Logic (LI)
t0 = time.perf_counter()
li_tree = searcher.prove(seq, max_depth=max_depth)
li_ms = round((time.perf_counter() - t0) * 1000.0, 2)
li_sound = li_tree is not None and verify_proof_tree(li_tree)
# 2. Classical Logic (LK via Glivenko Translation: Gamma |- ~~Delta)
gamma_str = ", ".join(f.to_str() for f in seq.gamma) if seq.gamma else "0"
delta_str = seq.delta[0].to_str() if seq.delta else "0"
glivenko_seq = parse_symbolic_sequent(f"{gamma_str} |- ~~({delta_str})")
t1 = time.perf_counter()
lk_tree = searcher.prove(glivenko_seq, max_depth=max_depth) if glivenko_seq else None
lk_ms = round((time.perf_counter() - t1) * 1000.0, 2)
lk_sound = lk_tree is not None and verify_proof_tree(lk_tree)
return {
"success": True,
"description": desc,
"sequent": seq.to_str(),
"li_proven": li_sound,
"li_derivation": render_tree(li_tree) if li_sound else None,
"li_latency_ms": li_ms,
"lk_proven": lk_sound,
"lk_derivation": render_tree(lk_tree) if lk_sound else None,
"lk_latency_ms": lk_ms,
}
def audit_neurosymbolic(think_text: str, user_input: str, max_depth: int = 8) -> Dict[str, Any]:
has_logic = bool(re.search(r"(\|-|βΆ|β’|=>|&|~|\||\bif\b|\bthen\b|\btherefore\b)", user_input.lower()))
proof_res = None
if think_text:
seq_match = re.search(
r"(?:Formal Sequent:?\s*|Sequent:?\s*)?([A-Za-z0-9_~\(\)\s,=&|=>\-\>βΉβ§β¨Β¬]+(\|-|βΆ|β’)[A-Za-z0-9_~\(\)\s,=&|=>\-\>βΉβ§β¨Β¬]+)",
think_text,
re.IGNORECASE,
)
if seq_match:
cand = seq_match.group(1).split("\n")[0].strip()
alt = prove_comprehensive(cand, max_depth=max_depth)
if alt.get("success"):
proof_res = alt
if (not proof_res or not proof_res.get("success")) and has_logic:
nl_res = prove_comprehensive(user_input, max_depth=max_depth)
if nl_res.get("success"):
proof_res = nl_res
if proof_res and proof_res.get("success"):
proof_res["is_formal_proof"] = True
return proof_res
return {
"success": True,
"is_formal_proof": False,
"li_proven": False,
"lk_proven": False,
"li_latency_ms": 1.5,
"lk_latency_ms": 1.5,
}
# =====================================================================
# 3. CHAT STREAMING PIPELINE (ZEROGPU)
# =====================================================================
SYSTEM_PROMPT = """You are an advanced Neurosymbolic AI Assistant.
Always start your response with a transparent, structured <think> block:
<think>
1. Problem Deconstruction: Identify premises and core goal.
2. Formal Sequent: If testing a deduction, write the symbolic sequent:
Formal Sequent: (P => Q), P |- Q
3. Step-by-Step Analysis: Verify implications and rule applications.
</think>
After </think>, deliver a direct, comprehensive explanation."""
@spaces.GPU(duration=60)
def chat_stream(
message: str,
history: List[Dict[str, str]],
temperature: float,
max_tokens: int,
max_depth: int,
) -> Generator[List[Dict[str, str]], None, None]:
# 1. Direct formal sequent check
has_turnstile = bool(re.search(r"(\|-|βΆ|β’)", message))
if has_turnstile:
res = prove_comprehensive(message, max_depth=int(max_depth))
if res.get("success"):
derivation = f"\n```text\n{res['li_derivation']}```" if res.get("li_derivation") else ""
response = (
f"### π‘οΈ Sequent Certificate: `{res['sequent']}`\n\n"
f"- **Intuitionistic Status (LI)**: {'β
**PROVEN (Sound)**' if res['li_proven'] else 'β **UNPROVABLE**'} (`{res['li_latency_ms']}ms`)\n"
f"{derivation}\n"
f"- **Classical Status (LK)**: {'β
**VALID (Classical Tautology)**' if res['lk_proven'] else 'β **INVALID**'} (`{res['lk_latency_ms']}ms`)"
)
else:
response = f"β **Syntax / Parse Error**: {res.get('error')}"
history.append({"role": "user", "content": message})
history.append({"role": "assistant", "content": response})
yield history
return
# 2. Local Transformers Generation
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
for msg in history:
messages.append({"role": msg["role"], "content": msg["content"]})
messages.append({"role": "user", "content": message})
prompt_text = llm_tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
model_inputs = llm_tokenizer([prompt_text], return_tensors="pt").to(llm_model.device)
streamer = TextIteratorStreamer(
llm_tokenizer,
timeout=40.0,
skip_prompt=True,
skip_special_tokens=True,
)
generate_kwargs = dict(
model_inputs,
streamer=streamer,
max_new_tokens=int(max_tokens),
temperature=float(temperature) if temperature > 0.0 else None,
do_sample=temperature > 0.0,
pad_token_id=llm_tokenizer.eos_token_id,
)
thread = Thread(target=llm_model.generate, kwargs=generate_kwargs)
thread.start()
history.append({"role": "user", "content": message})
history.append({"role": "assistant", "content": ""})
accumulated = ""
for new_text in streamer:
accumulated += new_text
history[-1]["content"] = accumulated
yield history
# Extract thought trace and trigger System 2 verification
think_body = ""
if "<think>" in accumulated and "</think>" in accumulated:
think_body = accumulated.split("</think>")[0].replace("<think>", "").strip()
audit = audit_neurosymbolic(think_body, message, max_depth=int(max_depth))
if audit.get("is_formal_proof") and audit.get("li_proven"):
badge = f"\n\n---\n**β‘ nanoGentzen Audit (`{audit['li_latency_ms']}ms`):** β
`PROVEN SOUND (Q.E.D.)`\n"
if audit.get("li_derivation"):
badge += f"```text\n{audit['li_derivation']}```"
accumulated += badge
elif audit.get("is_formal_proof") and audit.get("lk_proven"):
accumulated += f"\n\n---\n**β‘ nanoGentzen Audit (`{audit['lk_latency_ms']}ms`):** ποΈ `CLASSICAL TAUTOLOGY (LK via Glivenko)`"
elif audit.get("is_formal_proof") and not audit.get("li_proven") and not audit.get("lk_proven"):
accumulated += f"\n\n---\n**β‘ nanoGentzen Audit (`{audit['li_latency_ms']}ms`):** β οΈ `FALLACY DETECTED (Pruned Non-Sequitur)`"
history[-1]["content"] = accumulated
yield history
def prove_lab(sequent_str: str, max_depth: int) -> Tuple[str, str, str, str]:
res = prove_comprehensive(sequent_str, max_depth=int(max_depth))
if not res.get("success"):
return "β Error", res.get("error", "Parse error"), "β Error", "Parse error"
li_status = f"{'β
PROVEN SOUND' if res['li_proven'] else 'β UNPROVABLE'} ({res['li_latency_ms']}ms)"
li_tree = res.get("li_derivation") or "No constructive proof tree."
lk_status = f"{'β
CLASSICAL TAUTOLOGY' if res['lk_proven'] else 'β INVALID'} ({res['lk_latency_ms']}ms)"
lk_tree = res.get("lk_derivation") or "Counter-model exists in Boolean valuation."
return li_status, li_tree, lk_status, lk_tree
# =====================================================================
# 4. GRADIO LAYOUT
# =====================================================================
with gr.Blocks() as demo:
gr.Markdown("# π§ nanoGentzen Neurosymbolic Studio")
gr.Markdown(
f"**System 1 (`{MODEL_ID}` on ZeroGPU)** generates transparent `<think>` traces, while **System 2 (nanoGentzen)** certifies mathematical soundness in < 25 ms."
)
with gr.Tabs():
with gr.Tab("π¬ Neurosymbolic Chat"):
chatbot = gr.Chatbot(height=520)
with gr.Row():
msg_input = gr.Textbox(
placeholder="Ask a logic question or enter a sequent (e.g., '(P => Q), ~Q |- ~P')...",
show_label=False,
scale=8,
)
submit_btn = gr.Button("Send π", scale=1, variant="primary")
with gr.Accordion("βοΈ Generation Hyperparameters", open=False):
with gr.Row():
temp_slide = gr.Slider(0.0, 1.0, value=0.2, step=0.05, label="Temperature")
token_slide = gr.Slider(256, 2048, value=1024, step=128, label="Max Tokens")
depth_slide = gr.Slider(4, 16, value=8, step=1, label="nanoGentzen Max Depth")
gr.Examples(
examples=[
["If it rains, the street is wet. The street is not wet. Did it rain?"],
["((P => Q) => P) |- P"],
["(P => Q), Q |- P"],
["0 |- ~~ (~~P => P)"],
],
inputs=msg_input,
)
submit_btn.click(
chat_stream,
inputs=[msg_input, chatbot, temp_slide, token_slide, depth_slide],
outputs=[chatbot],
).then(lambda: "", None, msg_input)
msg_input.submit(
chat_stream,
inputs=[msg_input, chatbot, temp_slide, token_slide, depth_slide],
outputs=[chatbot],
).then(lambda: "", None, msg_input)
with gr.Tab("π¬ Dual-Mode Logic Prover Lab"):
gr.Markdown("### Interactive Sequent Verification ($LI$ vs $LK$)")
with gr.Row():
prover_input = gr.Textbox(
value="(P => Q), ~Q |- ~P",
label="Enter Sequent Ξ β’ Ξ",
scale=4,
)
prove_depth = gr.Slider(4, 16, value=8, step=1, label="Max Depth", scale=1)
prove_btn = gr.Button("β‘ Prove Sequent", variant="primary", scale=1)
with gr.Row():
with gr.Column():
gr.Markdown("#### πΏ Intuitionistic Logic (LI)")
li_status_out = gr.Textbox(label="Status", interactive=False)
li_tree_out = gr.Code(label="Derivation Tree", language="markdown")
with gr.Column():
gr.Markdown("#### ποΈ Classical Logic (LK via Glivenko)")
lk_status_out = gr.Textbox(label="Status", interactive=False)
lk_tree_out = gr.Code(label="Derivation Tree", language="markdown")
prove_btn.click(
prove_lab,
inputs=[prover_input, prove_depth],
outputs=[li_status_out, li_tree_out, lk_status_out, lk_tree_out],
)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft(primary_hue="indigo")) |