atakan Claude Sonnet 5 commited on
Commit
e1f9681
·
1 Parent(s): 4a647b7

fix: Wire up the GGUF deployment path and fix its tokenizer mismatch

Browse files

llama-cpp-python was never in requirements.txt, so on HF Spaces HAS_LLAMA_CPP
was always False and every request silently fell back to slow bf16 PyTorch
inference under ZeroGPU's 300s budget -- despite the app already containing a
full GGUF loading path for exactly this deployment. Added the dependency and
uploaded a quantized GGUF of the currently-best-benchmarked (sft_v2) fused
checkpoint to the atakankahya/ControlAI-Agent Hub repo.

Also fixed: passing an explicit .gguf/ollama model path loaded a hardcoded
"Qwen/Qwen2.5-3B-Instruct" tokenizer instead of this model's own tokenizer and
chat template -- wrong vocab and tool-call format entirely.

Quantization level was chosen empirically, not assumed: tested Q4_K_M, Q6_K,
Q8_0, and full f16 against this exact fine-tuned model on a step-response
prompt (3 runs each). Q4_K_M mis-routed tool calls in 3/3 runs and produced
wildly inconsistent hallucinated numbers; Q6_K/Q8_0/f16 all called the correct
tool with consistent, verified numbers in 3/3 runs. Q8_0 was fastest of the
three correct options and is near-lossless relative to f16 at roughly half
the size, so it's now the default.

Also corrected a stale "68,000+ chunks" README claim -- the actual local RAG
index has ~9,900.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Files changed (3) hide show
  1. README.md +2 -2
  2. controlai_agent/orchestrator.py +26 -5
  3. requirements.txt +1 -0
README.md CHANGED
@@ -50,7 +50,7 @@ Standard large language models (LLMs) operate probabilistically without determin
50
  1. **Deterministic Scientific Sandbox:** Computes continuous/discrete algebraic Riccati equations (CARE/DARE), matrix exponentials, and Bode diagrams using LAPACK, SciPy, and CVXPY.
51
  2. **4-Stage Mathematical Proof Standard:** Formulates system class, analytical theorems, closed-form derivations, and engineering breakdown limits.
52
  3. **Dynamic Simulation & Plotting:** Solves nonlinear differential equations and renders verified trajectories directly in the interface.
53
- 4. **Offline RAG Knowledge Engine:** Grounded with 68,000+ chunks indexed across classical and modern control engineering literature.
54
 
55
  ---
56
 
@@ -64,7 +64,7 @@ graph TD
64
  subgraph Core Engine [Hybrid Verification Engine]
65
  Brain[Foundation Model - Qwen3-4B]
66
  LoRA[Theory & Tool Adapter - SFT]
67
- RAG[Offline Knowledge Engine - 68,000+ Chunks]
68
  Tools[Deterministic Numerical Tool Suite]
69
  end
70
 
 
50
  1. **Deterministic Scientific Sandbox:** Computes continuous/discrete algebraic Riccati equations (CARE/DARE), matrix exponentials, and Bode diagrams using LAPACK, SciPy, and CVXPY.
51
  2. **4-Stage Mathematical Proof Standard:** Formulates system class, analytical theorems, closed-form derivations, and engineering breakdown limits.
52
  3. **Dynamic Simulation & Plotting:** Solves nonlinear differential equations and renders verified trajectories directly in the interface.
53
+ 4. **Offline RAG Knowledge Engine:** Grounded with 9,900+ chunks indexed across classical and modern control engineering literature.
54
 
55
  ---
56
 
 
64
  subgraph Core Engine [Hybrid Verification Engine]
65
  Brain[Foundation Model - Qwen3-4B]
66
  LoRA[Theory & Tool Adapter - SFT]
67
+ RAG[Offline Knowledge Engine - 9,900+ Chunks]
68
  Tools[Deterministic Numerical Tool Suite]
69
  end
70
 
controlai_agent/orchestrator.py CHANGED
@@ -239,6 +239,11 @@ REPETITION_PENALTY = 1.15
239
 
240
  PROJECT_ROOT = Path(__file__).resolve().parent.parent
241
 
 
 
 
 
 
242
  # How many times a single tool may be invoked within one user turn. Two allows
243
  # a legitimate retry with corrected arguments after an error, while stopping
244
  # the model from spending its whole step budget re-running the same lookup.
@@ -572,7 +577,7 @@ class ControlAIAgent:
572
 
573
  if self.is_ollama:
574
  self.ollama_model = model_path.split(":", 1)[1] if ":" in str(model_path) else "controlai"
575
- self.hf_tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-3B-Instruct", trust_remote_code=True)
576
  elif self.is_gguf:
577
  if not HAS_LLAMA_CPP:
578
  raise ImportError("llama-cpp-python is required to run GGUF models. Install it with: pip install llama-cpp-python")
@@ -582,7 +587,12 @@ class ControlAIAgent:
582
  n_ctx=16384,
583
  verbose=False,
584
  )
585
- self.hf_tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-3B-Instruct", trust_remote_code=True)
 
 
 
 
 
586
  elif self.is_mlx:
587
  if adapter_path:
588
  self.model, self.mlx_tokenizer = mlx_load(model_path, adapter_path=adapter_path)
@@ -590,11 +600,22 @@ class ControlAIAgent:
590
  self.model, self.mlx_tokenizer = mlx_load(model_path)
591
  self.hf_tokenizer = AutoTokenizer.from_pretrained(model_path)
592
  else:
593
- # Universal Linux / Cloud / HuggingFace Spaces backend: Fast 4-bit C++ GGUF
594
  # of OUR OWN fine-tuned ControlAI model (never a generic base model).
595
- CONTROLAI_HF_REPO = "atakankahya/ControlAI-Agent"
 
 
 
 
 
 
 
 
 
 
 
596
  gguf_repo = os.environ.get("CONTROLAI_GGUF_REPO", CONTROLAI_HF_REPO)
597
- gguf_filename = os.environ.get("CONTROLAI_GGUF_FILENAME", "*controlai-q4_k_m.gguf")
598
 
599
  self.llama_model = None
600
  if HAS_LLAMA_CPP:
 
239
 
240
  PROJECT_ROOT = Path(__file__).resolve().parent.parent
241
 
242
+ # Our own fine-tuned model's repo -- every GGUF/Ollama/CUDA backend must load
243
+ # ITS tokenizer and chat template (never a generic base model's), since the
244
+ # tool-call format, special tokens, and vocab are specific to this fine-tune.
245
+ CONTROLAI_HF_REPO = "atakankahya/ControlAI-Agent"
246
+
247
  # How many times a single tool may be invoked within one user turn. Two allows
248
  # a legitimate retry with corrected arguments after an error, while stopping
249
  # the model from spending its whole step budget re-running the same lookup.
 
577
 
578
  if self.is_ollama:
579
  self.ollama_model = model_path.split(":", 1)[1] if ":" in str(model_path) else "controlai"
580
+ self.hf_tokenizer = AutoTokenizer.from_pretrained(CONTROLAI_HF_REPO, trust_remote_code=True)
581
  elif self.is_gguf:
582
  if not HAS_LLAMA_CPP:
583
  raise ImportError("llama-cpp-python is required to run GGUF models. Install it with: pip install llama-cpp-python")
 
587
  n_ctx=16384,
588
  verbose=False,
589
  )
590
+ # A local .gguf path carries no tokenizer/chat-template of its own --
591
+ # always load ours (never a generic base model's), same as the
592
+ # auto-downloaded deployment path below.
593
+ local_fused = PROJECT_ROOT / "models" / "controlai_fused"
594
+ tokenizer_source = str(local_fused) if local_fused.exists() else CONTROLAI_HF_REPO
595
+ self.hf_tokenizer = AutoTokenizer.from_pretrained(tokenizer_source, trust_remote_code=True)
596
  elif self.is_mlx:
597
  if adapter_path:
598
  self.model, self.mlx_tokenizer = mlx_load(model_path, adapter_path=adapter_path)
 
600
  self.model, self.mlx_tokenizer = mlx_load(model_path)
601
  self.hf_tokenizer = AutoTokenizer.from_pretrained(model_path)
602
  else:
603
+ # Universal Linux / Cloud / HuggingFace Spaces backend: fast C++ GGUF
604
  # of OUR OWN fine-tuned ControlAI model (never a generic base model).
605
+ #
606
+ # Q8_0, not Q4_K_M: measured directly against this exact model on the
607
+ # "simulate a step response" prompt (3 runs each). Q4_K_M mis-routed
608
+ # tool calls in 3/3 runs (reached for continuous_lqr/bode_analysis
609
+ # instead of simulate_step_response, then hallucinated inconsistent
610
+ # overshoot values -- 8.6%, 43.1%, 25.4%, none near the true ~9.5%).
611
+ # Q6_K, Q8_0, and full f16 all called the correct tool with
612
+ # consistent, tool-verified numbers in 3/3 runs apiece, matching
613
+ # local MLX behavior -- Q8_0 was picked among those three because it
614
+ # was both the fastest of the three in this test (8-19s vs Q6_K's
615
+ # 11-25s and f16's 11-22s) and, at 8.5 bits/weight, close enough to
616
+ # f16 to be considered near-lossless, at roughly half f16's size.
617
  gguf_repo = os.environ.get("CONTROLAI_GGUF_REPO", CONTROLAI_HF_REPO)
618
+ gguf_filename = os.environ.get("CONTROLAI_GGUF_FILENAME", "*controlai-q8_0.gguf")
619
 
620
  self.llama_model = None
621
  if HAS_LLAMA_CPP:
requirements.txt CHANGED
@@ -13,6 +13,7 @@ control>=0.9.4
13
  cvxpy>=1.4.0
14
  rich>=13.7.0
15
  rank-bm25>=0.2.2
 
16
  pypdf>=3.17.0
17
  pymupdf>=1.23.0
18
  scikit-learn>=1.3.0
 
13
  cvxpy>=1.4.0
14
  rich>=13.7.0
15
  rank-bm25>=0.2.2
16
+ llama-cpp-python>=0.3.0
17
  pypdf>=3.17.0
18
  pymupdf>=1.23.0
19
  scikit-learn>=1.3.0