mcamargo00 commited on
Commit
d6b181a
ยท
verified ยท
1 Parent(s): faf4392

Upload 4 files

Browse files
Files changed (1) hide show
  1. app.py +133 -176
app.py CHANGED
@@ -1,196 +1,153 @@
1
- # app.py โ”€โ”€ Math-solution classifier on HF Spaces (Zero-GPU-safe)
2
- #
3
- # Pin in requirements.txt:
4
- # gradio==4.44.0 torch==2.1.0 transformers==4.35.0 peft==0.7.1 accelerate==0.25.0 spaces
5
-
6
- import os
7
- import json
8
- import logging
9
- from typing import Tuple
10
 
11
  import gradio as gr
12
- import spaces # <- Hugging Face Spaces SDK (Zero)
13
-
14
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
15
- # CONSTANTS (no CUDA use here)
16
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
17
- ADAPTER_PATH = os.getenv("ADAPTER_PATH", "./lora_adapter") # dir or Hub repo
18
- FALLBACK_MODEL = "distilbert-base-uncased"
19
- LABELS = {0: "โœ… Correct",
20
- 1: "๐Ÿค” Conceptual Error",
21
- 2: "๐Ÿ”ข Computational Error"}
22
 
 
23
  logging.basicConfig(level=logging.INFO)
24
  logger = logging.getLogger(__name__)
25
 
26
- # Globals that will live **inside the GPU worker**
27
- model = None
28
  tokenizer = None
29
- model_ty = None # "classification" | "causal_lm" | "baseline"
30
-
31
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
32
- # GPU-SIDE INITIALISATION & INFERENCE
33
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
34
- def _load_model_gpu():
35
- """
36
- Runs **inside the GPU worker**.
37
- Tries LoRA classification adapter โ†’ LoRA causal-LM adapter โ†’ plain baseline.
38
- """
39
- global model, tokenizer, model_ty
40
- import torch
41
- from transformers import (
42
- AutoTokenizer,
43
- AutoModelForSequenceClassification,
44
- )
45
- from peft.auto import (
46
- AutoPeftModelForSequenceClassification,
47
- AutoPeftModelForCausalLM,
48
- )
49
-
50
- dtype = torch.float16
51
- if os.path.isdir(ADAPTER_PATH):
52
- logger.info(f"[GPU] Loading adapter from {ADAPTER_PATH}")
53
-
54
- try: # 1) classification adapter
55
- model = AutoPeftModelForSequenceClassification.from_pretrained(
56
- ADAPTER_PATH, torch_dtype=dtype, device_map="auto"
57
- )
58
- model_ty = "classification"
59
- except ValueError:
60
- logger.info("[GPU] Not a classifier, trying causal-LM")
61
- model = AutoPeftModelForCausalLM.from_pretrained(
62
- ADAPTER_PATH, torch_dtype=dtype, device_map="auto"
63
- )
64
- model_ty = "causal_lm"
65
-
66
- tokenizer = AutoTokenizer.from_pretrained(ADAPTER_PATH)
67
-
68
- else:
69
- logger.warning("[GPU] No adapter found โ€“ using baseline DistilBERT")
70
- tokenizer = AutoTokenizer.from_pretrained(FALLBACK_MODEL)
71
- model = AutoModelForSequenceClassification.from_pretrained(
72
- FALLBACK_MODEL, num_labels=3, ignore_mismatched_sizes=True
73
- )
74
- model_ty = "baseline"
75
-
76
- if tokenizer.pad_token is None:
77
- tokenizer.pad_token = tokenizer.eos_token or tokenizer.sep_token
78
-
79
- model.eval()
80
- logger.info(f"[GPU] Model ready ({model_ty})")
81
-
82
-
83
- def _classify_logits(question: str, solution: str) -> Tuple[str, str, str]:
84
- import torch
85
- text = f"Question: {question}\n\nSolution:\n{solution}"
86
- inputs = tokenizer(
87
- text, return_tensors="pt", padding=True, truncation=True, max_length=512
88
- ).to("cuda")
89
-
90
- with torch.no_grad():
91
- logits = model(**inputs).logits
92
- probs = torch.softmax(logits, dim=-1)[0]
93
- pred = int(torch.argmax(probs))
94
- conf = f"{probs[pred].item():.3f}"
95
-
96
- return LABELS[pred], conf, "โ€”"
97
-
98
 
99
- def _classify_generate(question: str, solution: str) -> Tuple[str, str, str]:
100
- import torch
101
- prompt = (
102
- "You are a mathematics tutor.\n"
103
- "You are given a math word problem and a student's solution. Decide whether the solution is correct.\n\n"
104
- "- Correct = all reasoning and calculations are correct.\n"
105
- "- Conceptual Error = reasoning is wrong.\n"
106
- "- Computational Error= reasoning okay but arithmetic off.\n\n"
107
- "Reply with ONLY one of these JSON lines:\n"
108
- '{"verdict": "correct"}\n'
109
- '{"verdict": "conceptual"}\n'
110
- '{"verdict": "computational"}\n\n'
111
- f"Question: {question}\n\nSolution:\n{solution}\n\nAnswer:"
112
- )
113
-
114
- inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
115
- with torch.no_grad():
116
- out_ids = model.generate(
117
- **inputs,
118
- max_new_tokens=32,
119
- pad_token_id=tokenizer.eos_token_id,
120
- )
121
- generated = tokenizer.decode(
122
- out_ids[0][inputs["input_ids"].shape[1]:],
123
- skip_special_tokens=True,
124
- ).strip()
125
-
126
- verdict = "Unparsed"
127
  try:
128
- data = json.loads(generated.splitlines()[-1])
129
- v = data.get("verdict", "").lower()
130
- if v.startswith("corr"):
131
- verdict = LABELS[0]
132
- elif v.startswith("conc"):
133
- verdict = LABELS[1]
134
- elif v.startswith("comp"):
135
- verdict = LABELS[2]
136
- except Exception:
137
- pass
138
-
139
- return verdict, "", generated
140
-
141
-
142
- @spaces.GPU # <-- every CUDA op happens inside here
143
- def gpu_classify(question: str, solution: str):
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  """
145
- Proxy target for Gradio. Executed in the GPU worker so CUDA is allowed.
146
- Returns (verdict, confidence, raw_output)
147
  """
148
- if model is None:
149
- _load_model_gpu()
150
-
151
  if not question.strip() or not solution.strip():
152
- return "Please fill both fields.", "", ""
153
-
154
- if model_ty in ("classification", "baseline"):
155
- return _classify_logits(question, solution)
156
- else: # causal_lm
157
- return _classify_generate(question, solution)
158
-
159
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
160
- # CPU-SIDE UI (no torch.cuda here)
161
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
162
- def classify_proxy(q, s):
163
- """Simple wrapper so Gradio can call the GPU function."""
164
- return gpu_classify(q, s)
165
-
166
- with gr.Blocks(title="Math Solution Classifier") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  gr.Markdown("# ๐Ÿงฎ Math Solution Classifier")
168
- gr.Markdown(
169
- "Classify a student's math solution as **correct**, **conceptually flawed**, "
170
- "or **computationally flawed**."
171
- )
172
-
173
  with gr.Row():
174
  with gr.Column():
175
- q_in = gr.Textbox(label="Math Question", lines=3)
176
- s_in = gr.Textbox(label="Proposed Solution", lines=6)
177
- btn = gr.Button("Classify", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
178
  with gr.Column():
179
- verdict = gr.Textbox(label="Verdict", interactive=False)
180
- conf = gr.Textbox(label="Confidence", interactive=False)
181
- raw = gr.Textbox(label="Model Output", interactive=False)
182
-
183
- btn.click(classify_proxy, [q_in, s_in], [verdict, conf, raw])
184
-
185
  gr.Examples(
186
- [
187
- ["Solve for x: 2x + 5 = 13", "2x + 5 = 13\n2x = 8\nx = 4"],
188
- ["Find the derivative of f(x)=xยฒ", "f'(x)=2x+1"],
189
- ["What is 15% of 200?", "0.15 ร— 200 = 30"],
 
 
 
 
 
 
 
 
 
190
  ],
191
- inputs=[q_in, s_in],
 
 
 
 
 
 
192
  )
193
- app = demo # Gradio SDK looks for โ€œappโ€
194
-
195
-
196
 
 
 
 
1
+ # app.py - Gradio version (much simpler for HF Spaces)
 
 
 
 
 
 
 
 
2
 
3
  import gradio as gr
4
+ import torch
5
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
6
+ import logging
 
 
 
 
 
 
 
7
 
8
+ # Set up logging
9
  logging.basicConfig(level=logging.INFO)
10
  logger = logging.getLogger(__name__)
11
 
12
+ # Global variables for model and tokenizer
13
+ model = None
14
  tokenizer = None
15
+ label_mapping = {0: "โœ… Correct", 1: "๐Ÿค” Conceptually Flawed", 2: "๐Ÿ”ข Computationally Flawed"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ def load_model():
18
+ """Load your trained model here"""
19
+ global model, tokenizer
20
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  try:
22
+ # Replace these with your actual model path/name
23
+ # Option 1: Load from local files
24
+ # model = AutoModelForSequenceClassification.from_pretrained("./your_model_directory")
25
+ # tokenizer = AutoTokenizer.from_pretrained("./your_model_directory")
26
+
27
+ # Option 2: Load from Hugging Face Hub (if you upload your model there)
28
+ # model = AutoModelForSequenceClassification.from_pretrained("your-username/your-model-name")
29
+ # tokenizer = AutoTokenizer.from_pretrained("your-username/your-model-name")
30
+
31
+ # For now, we'll use a placeholder - replace this with your actual model loading
32
+ logger.warning("Using placeholder model loading - replace with your actual model!")
33
+
34
+ # Placeholder model loading (replace this!)
35
+ model_name = "distilbert-base-uncased" # Replace with your model
36
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
37
+ model = AutoModelForSequenceClassification.from_pretrained(
38
+ model_name,
39
+ num_labels=3,
40
+ ignore_mismatched_sizes=True
41
+ )
42
+
43
+ logger.info("Model loaded successfully")
44
+ return "Model loaded successfully!"
45
+
46
+ except Exception as e:
47
+ logger.error(f"Error loading model: {e}")
48
+ return f"Error loading model: {e}"
49
+
50
+ def classify_solution(question: str, solution: str):
51
  """
52
+ Classify the math solution
53
+ Returns: (classification_label, confidence_score, explanation)
54
  """
 
 
 
55
  if not question.strip() or not solution.strip():
56
+ return "Please fill in both fields", 0.0, ""
57
+
58
+ if not model or not tokenizer:
59
+ return "Model not loaded", 0.0, ""
60
+
61
+ try:
62
+ # Combine question and solution for input
63
+ text_input = f"Question: {question}\nSolution: {solution}"
64
+
65
+ # Tokenize input
66
+ inputs = tokenizer(
67
+ text_input,
68
+ return_tensors="pt",
69
+ truncation=True,
70
+ padding=True,
71
+ max_length=512
72
+ )
73
+
74
+ # Get model prediction
75
+ with torch.no_grad():
76
+ outputs = model(**inputs)
77
+ predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
78
+ predicted_class = torch.argmax(predictions, dim=-1).item()
79
+ confidence = predictions[0][predicted_class].item()
80
+
81
+ classification = label_mapping[predicted_class]
82
+
83
+ # Create explanation based on classification
84
+ explanations = {
85
+ 0: "The mathematical approach and calculations are both sound.",
86
+ 1: "The approach or understanding has fundamental issues.",
87
+ 2: "The approach is correct, but there are calculation errors."
88
+ }
89
+
90
+ explanation = explanations[predicted_class]
91
+
92
+ return classification, f"{confidence:.2%}", explanation
93
+
94
+ except Exception as e:
95
+ logger.error(f"Error during classification: {e}")
96
+ return f"Classification error: {str(e)}", "0%", ""
97
+
98
+ # Load model on startup
99
+ load_model()
100
+
101
+ # Create Gradio interface
102
+ with gr.Blocks(title="Math Solution Classifier", theme=gr.themes.Soft()) as app:
103
  gr.Markdown("# ๐Ÿงฎ Math Solution Classifier")
104
+ gr.Markdown("Classify math solutions as correct, conceptually flawed, or computationally flawed.")
105
+
 
 
 
106
  with gr.Row():
107
  with gr.Column():
108
+ question_input = gr.Textbox(
109
+ label="Math Question",
110
+ placeholder="e.g., Solve for x: 2x + 5 = 13",
111
+ lines=3
112
+ )
113
+
114
+ solution_input = gr.Textbox(
115
+ label="Proposed Solution",
116
+ placeholder="e.g., 2x + 5 = 13\n2x = 13 - 5\n2x = 8\nx = 4",
117
+ lines=5
118
+ )
119
+
120
+ classify_btn = gr.Button("Classify Solution", variant="primary")
121
+
122
  with gr.Column():
123
+ classification_output = gr.Textbox(label="Classification", interactive=False)
124
+ confidence_output = gr.Textbox(label="Confidence", interactive=False)
125
+ explanation_output = gr.Textbox(label="Explanation", interactive=False, lines=3)
126
+
127
+ # Examples
 
128
  gr.Examples(
129
+ examples=[
130
+ [
131
+ "Solve for x: 2x + 5 = 13",
132
+ "2x + 5 = 13\n2x = 13 - 5\n2x = 8\nx = 4"
133
+ ],
134
+ [
135
+ "Find the derivative of f(x) = xยฒ",
136
+ "f'(x) = 2x + 1" # This should be computationally flawed
137
+ ],
138
+ [
139
+ "What is 15% of 200?",
140
+ "15% = 15/100 = 0.15\n0.15 ร— 200 = 30"
141
+ ]
142
  ],
143
+ inputs=[question_input, solution_input]
144
+ )
145
+
146
+ classify_btn.click(
147
+ fn=classify_solution,
148
+ inputs=[question_input, solution_input],
149
+ outputs=[classification_output, confidence_output, explanation_output]
150
  )
 
 
 
151
 
152
+ if __name__ == "__main__":
153
+ app.launch()