Papajams commited on
Commit
ea98bbc
·
verified ·
1 Parent(s): b11e593

Switch to SmolLM2-360M (prebuilt wheels, no C++ compile)

Browse files
Files changed (4) hide show
  1. README.md +12 -5
  2. app.py +2 -2
  3. health_coach.py +40 -54
  4. requirements.txt +2 -1
README.md CHANGED
@@ -14,8 +14,9 @@ tags:
14
  - tiny-titan
15
  - best-agent
16
  - off-brand
 
17
  models:
18
- - hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF
19
  ---
20
 
21
  # 🫀 Body Debt
@@ -33,16 +34,16 @@ Body Debt calculates the precise recovery cost of last night's choices — alcoh
33
 
34
  ## The model
35
 
36
- **Llama-3.2-1B-Instruct** (Q4_K_M quantization, ~700MB) — runs entirely on CPU via `llama-cpp-python`. No API calls, no cloud inference. Your health data never leaves your machine.
37
 
38
  The face scan stress classifier is a custom 7→16→8→1 MLP (~2KB ONNX) that converts facial geometry features into a fatigue score.
39
 
40
  ## Tech
41
 
42
- - **LLM**: Llama-3.2-1B-Instruct (1B params, Q4_K_M GGUF) via llama-cpp-python
43
  - **Face analysis**: MediaPipe FaceMesh → 7 stress features → ONNX MLP
44
  - **Scoring**: Deterministic 5-system engine with physiological weights, drink-type modifiers, training CNS load, circadian alignment penalties
45
- - **UI**: Gradio 5 with custom dark theme
46
 
47
  ## Privacy
48
 
@@ -66,9 +67,15 @@ python generate_model.py # creates the ONNX stress model
66
  python app.py
67
  ```
68
 
 
 
 
 
 
 
69
  ## Full product
70
 
71
- The complete Body Debt application (Next.js, ZK proofs on SKALE, real-time animated dashboard) is at: [github.com/body-debt](https://github.com)
72
 
73
  ---
74
 
 
14
  - tiny-titan
15
  - best-agent
16
  - off-brand
17
+ - openai-codex
18
  models:
19
+ - HuggingFaceTB/SmolLM2-360M-Instruct
20
  ---
21
 
22
  # 🫀 Body Debt
 
34
 
35
  ## The model
36
 
37
+ **SmolLM2-360M-Instruct** (360M parameters) — runs entirely on CPU via HuggingFace Transformers. No external API calls, no cloud inference. Your health data stays on-device.
38
 
39
  The face scan stress classifier is a custom 7→16→8→1 MLP (~2KB ONNX) that converts facial geometry features into a fatigue score.
40
 
41
  ## Tech
42
 
43
+ - **LLM**: SmolLM2-360M-Instruct (360M params) via HuggingFace Transformers
44
  - **Face analysis**: MediaPipe FaceMesh → 7 stress features → ONNX MLP
45
  - **Scoring**: Deterministic 5-system engine with physiological weights, drink-type modifiers, training CNS load, circadian alignment penalties
46
+ - **UI**: Gradio 6 with custom dark theme
47
 
48
  ## Privacy
49
 
 
67
  python app.py
68
  ```
69
 
70
+ ## OpenAI Codex Track
71
+
72
+ This Space was built with OpenAI Codex as the coding agent. The public source repository, including Codex-attributed commits, is here:
73
+
74
+ **Repository:** [github.com/udirobert/bodydebt](https://github.com/udirobert/bodydebt)
75
+
76
  ## Full product
77
 
78
+ The complete Body Debt application (Next.js, ZK proofs on SKALE, real-time animated dashboard) is at: [github.com/udirobert/bodydebt](https://github.com/udirobert/bodydebt)
79
 
80
  ---
81
 
app.py CHANGED
@@ -198,7 +198,7 @@ def run_analysis(
198
  progress(1.0, desc="Done!")
199
 
200
  advice_md = "### 🤖 Recovery Prescription\n\n"
201
- advice_md += f"*Generated by Llama-3.2-1B running locally*\n\n{advice}"
202
 
203
  return score_md, face_text, advice_md
204
 
@@ -323,7 +323,7 @@ with gr.Blocks(title="Body Debt") as demo:
323
  gr.Markdown(
324
  """
325
  ---
326
- *Body Debt uses Llama-3.2-1B (1 billion parameters) running locally via llama-cpp-python.
327
  Face analysis uses MediaPipe FaceMesh — no biometric data leaves your device.
328
  Built for the [Build Small Hackathon](https://huggingface.co/spaces/huggingface/build-small-hackathon).*
329
  """
 
198
  progress(1.0, desc="Done!")
199
 
200
  advice_md = "### 🤖 Recovery Prescription\n\n"
201
+ advice_md += f"*Generated by SmolLM2-360M running locally*\n\n{advice}"
202
 
203
  return score_md, face_text, advice_md
204
 
 
323
  gr.Markdown(
324
  """
325
  ---
326
+ *Body Debt uses SmolLM2-360M-Instruct (360M parameters) running locally via HuggingFace Transformers.
327
  Face analysis uses MediaPipe FaceMesh — no biometric data leaves your device.
328
  Built for the [Build Small Hackathon](https://huggingface.co/spaces/huggingface/build-small-hackathon).*
329
  """
health_coach.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Local LLM health coach using llama-cpp-python.
3
  Generates personalized recovery advice from stressor + face scan data.
4
  Falls back to a template-based response if model unavailable.
5
  """
@@ -7,27 +7,9 @@ Falls back to a template-based response if model unavailable.
7
  from __future__ import annotations
8
 
9
  import os
10
- from pathlib import Path
11
  from typing import Optional
12
 
13
- from huggingface_hub import hf_hub_download
14
-
15
- MODEL_REPO = "hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF"
16
- MODEL_FILE = "llama-3.2-1b-instruct-q4_k_m.gguf"
17
- CACHE_DIR = Path.home() / ".cache" / "body-debt-models"
18
-
19
-
20
- def get_model_path() -> Path:
21
- local = CACHE_DIR / MODEL_FILE
22
- if local.exists():
23
- return local
24
- CACHE_DIR.mkdir(parents=True, exist_ok=True)
25
- path = hf_hub_download(
26
- repo_id=MODEL_REPO,
27
- filename=MODEL_FILE,
28
- local_dir=str(CACHE_DIR),
29
- )
30
- return Path(path)
31
 
32
 
33
  def generate_advice(
@@ -37,66 +19,70 @@ def generate_advice(
37
  face_stress: Optional[float] = None,
38
  progress_callback=None,
39
  ) -> str:
40
- """Generate personalized recovery advice using local Llama-3.2-1B."""
41
  try:
42
  if progress_callback:
43
  progress_callback(0.1, "Loading model...")
44
- model_path = get_model_path()
45
- if progress_callback:
46
- progress_callback(0.5, "Model loaded, generating advice...")
47
- return _llm_generate(model_path, debt_score, system_scores, stressor_summary, face_stress)
48
  except Exception as e:
 
49
  return _fallback_advice(debt_score, system_scores, stressor_summary)
50
 
51
 
52
- def _build_prompt(
53
  debt_score: int,
54
  system_scores: list[dict],
55
  stressor_summary: str,
56
  face_stress: Optional[float],
57
- ) -> str:
58
  systems_text = "\n".join(
59
  f"- {s['label']}: {s['score']}/100 (clears {s['cleared_at']})"
60
  for s in system_scores
61
  )
62
  face_text = f"\nFacial stress indicator: {face_stress:.0f}/100" if face_stress else ""
63
 
64
- return f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
65
- You are a concise recovery coach. Given physiological debt data, provide specific, actionable recovery advice in 4 categories: Right Now, This Morning, Today, Avoid. Be direct, no fluff. Use the system scores to prioritize which body systems need attention most urgently.<|eot_id|><|start_header_id|>user<|end_header_id|>
66
- My body debt score: {debt_score}/100
67
- Stressors: {stressor_summary}{face_text}
68
-
69
- System breakdown:
70
- {systems_text}
71
-
72
- Give me my recovery prescription.<|eot_id|><|start_header_id|>assistant<|end_header_id|>
73
- """
74
 
75
 
76
- def _llm_generate(
77
- model_path: Path,
78
  debt_score: int,
79
  system_scores: list[dict],
80
  stressor_summary: str,
81
  face_stress: Optional[float],
 
82
  ) -> str:
83
- from llama_cpp import Llama
84
 
85
- llm = Llama(
86
- model_path=str(model_path),
87
- n_ctx=2048,
88
- n_threads=4,
89
- verbose=False,
90
- )
91
- prompt = _build_prompt(debt_score, system_scores, stressor_summary, face_stress)
92
- output = llm(
93
- prompt,
94
- max_tokens=512,
95
- temperature=0.7,
96
- top_p=0.9,
97
- stop=["<|eot_id|>"],
98
  )
99
- return output["choices"][0]["text"].strip()
 
 
 
 
 
 
 
 
 
 
 
100
 
101
 
102
  def _fallback_advice(debt_score: int, system_scores: list[dict], stressor_summary: str) -> str:
 
1
  """
2
+ Local LLM health coach using HuggingFace Transformers.
3
  Generates personalized recovery advice from stressor + face scan data.
4
  Falls back to a template-based response if model unavailable.
5
  """
 
7
  from __future__ import annotations
8
 
9
  import os
 
10
  from typing import Optional
11
 
12
+ MODEL_ID = "HuggingFaceTB/SmolLM2-360M-Instruct"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
 
15
  def generate_advice(
 
19
  face_stress: Optional[float] = None,
20
  progress_callback=None,
21
  ) -> str:
22
+ """Generate personalized recovery advice using a small local LLM."""
23
  try:
24
  if progress_callback:
25
  progress_callback(0.1, "Loading model...")
26
+ return _transformers_generate(debt_score, system_scores, stressor_summary, face_stress, progress_callback)
 
 
 
27
  except Exception as e:
28
+ print(f"LLM generation failed: {e}")
29
  return _fallback_advice(debt_score, system_scores, stressor_summary)
30
 
31
 
32
+ def _build_messages(
33
  debt_score: int,
34
  system_scores: list[dict],
35
  stressor_summary: str,
36
  face_stress: Optional[float],
37
+ ) -> list[dict]:
38
  systems_text = "\n".join(
39
  f"- {s['label']}: {s['score']}/100 (clears {s['cleared_at']})"
40
  for s in system_scores
41
  )
42
  face_text = f"\nFacial stress indicator: {face_stress:.0f}/100" if face_stress else ""
43
 
44
+ return [
45
+ {
46
+ "role": "system",
47
+ "content": "You are a concise recovery coach. Given physiological debt data, provide specific, actionable recovery advice in 4 categories: Right Now, This Morning, Today, Avoid. Be direct, no fluff. Use the system scores to prioritize which body systems need attention most urgently.",
48
+ },
49
+ {
50
+ "role": "user",
51
+ "content": f"My body debt score: {debt_score}/100\nStressors: {stressor_summary}{face_text}\n\nSystem breakdown:\n{systems_text}\n\nGive me my recovery prescription.",
52
+ },
53
+ ]
54
 
55
 
56
+ def _transformers_generate(
 
57
  debt_score: int,
58
  system_scores: list[dict],
59
  stressor_summary: str,
60
  face_stress: Optional[float],
61
+ progress_callback=None,
62
  ) -> str:
63
+ from transformers import pipeline
64
 
65
+ if progress_callback:
66
+ progress_callback(0.3, "Loading SmolLM2-360M...")
67
+
68
+ pipe = pipeline(
69
+ "text-generation",
70
+ model=MODEL_ID,
71
+ device_map="auto",
72
+ torch_dtype="auto",
 
 
 
 
 
73
  )
74
+
75
+ if progress_callback:
76
+ progress_callback(0.6, "Generating advice...")
77
+
78
+ messages = _build_messages(debt_score, system_scores, stressor_summary, face_stress)
79
+ output = pipe(messages, max_new_tokens=300, temperature=0.7, do_sample=True)
80
+ result = output[0]["generated_text"][-1]["content"]
81
+
82
+ if progress_callback:
83
+ progress_callback(1.0, "Done!")
84
+
85
+ return result
86
 
87
 
88
  def _fallback_advice(debt_score: int, system_scores: list[dict], stressor_summary: str) -> str:
requirements.txt CHANGED
@@ -2,5 +2,6 @@ gradio>=5.0.0,<7.0.0
2
  mediapipe>=0.10.14
3
  numpy>=1.26.0
4
  onnxruntime>=1.18.0
5
- llama-cpp-python>=0.3.0
6
  huggingface_hub>=0.25.0
 
 
 
2
  mediapipe>=0.10.14
3
  numpy>=1.26.0
4
  onnxruntime>=1.18.0
 
5
  huggingface_hub>=0.25.0
6
+ transformers>=4.45.0
7
+ accelerate>=1.0.0