mrs83 commited on
Commit
291db75
·
verified ·
1 Parent(s): 6fe79ba

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +411 -0
app.py ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import time
3
+ from threading import Thread
4
+
5
+ import gradio as gr
6
+ import matplotlib
7
+ import numpy as np
8
+ import torch
9
+
10
+ try:
11
+ import whisper
12
+
13
+ HAS_WHISPER = True
14
+ except ImportError:
15
+ HAS_WHISPER = False
16
+
17
+ from peft import PeftModel
18
+ from transformers import (
19
+ AutoModelForCausalLM,
20
+ AutoTokenizer,
21
+ TextIteratorStreamer,
22
+ )
23
+
24
+ # Use non-interactive backend for matplotlib
25
+ matplotlib.use("Agg")
26
+ import matplotlib.pyplot as plt
27
+
28
+ # ================================================================
29
+ # CONFIGURATION
30
+ # ================================================================
31
+ BASE_MODEL = "ethicalabs/Echo-DSRN-114M-v0.1.2"
32
+ ADAPTER_PATH = "ethicalabs/Echo-SmolTools-114M-Intent-PEFT"
33
+
34
+ # ================================================================
35
+ # METADATA: INTENTS & EXAMPLES
36
+ # ================================================================
37
+ INTENTS = [
38
+ "datetime_query",
39
+ "iot_hue_lightchange",
40
+ "transport_ticket",
41
+ "takeaway_query",
42
+ "qa_stock",
43
+ "general_greet",
44
+ "recommendation_events",
45
+ "music_dislikeness",
46
+ "iot_wemo_off",
47
+ "cooking_recipe",
48
+ "qa_currency",
49
+ "transport_traffic",
50
+ "general_quirky",
51
+ "weather_query",
52
+ "audio_volume_up",
53
+ "email_addcontact",
54
+ "takeaway_order",
55
+ "email_querycontact",
56
+ "iot_hue_lightup",
57
+ "recommendation_locations",
58
+ "play_audiobook",
59
+ "lists_createoradd",
60
+ "news_query",
61
+ "alarm_query",
62
+ "iot_wemo_on",
63
+ "general_joke",
64
+ "qa_definition",
65
+ "social_query",
66
+ "music_settings",
67
+ "audio_volume_other",
68
+ "calendar_remove",
69
+ "iot_hue_lightdim",
70
+ "calendar_query",
71
+ "email_sendemail",
72
+ "iot_cleaning",
73
+ "audio_volume_down",
74
+ "play_radio",
75
+ "cooking_query",
76
+ "datetime_convert",
77
+ "qa_maths",
78
+ "iot_hue_lightoff",
79
+ "iot_hue_lighton",
80
+ "transport_query",
81
+ "music_likeness",
82
+ "email_query",
83
+ "play_music",
84
+ "audio_volume_mute",
85
+ "social_post",
86
+ "alarm_set",
87
+ "qa_factoid",
88
+ "calendar_set",
89
+ "play_game",
90
+ "alarm_remove",
91
+ "lists_remove",
92
+ "transport_taxi",
93
+ "recommendation_movies",
94
+ "iot_coffee",
95
+ "music_query",
96
+ "play_podcasts",
97
+ "lists_query",
98
+ ]
99
+
100
+ EXAMPLES = {
101
+ "it-IT": [
102
+ "spegni le luci per favore",
103
+ "abbassa le luci dell' ingresso",
104
+ "riproduci oro di mango",
105
+ "quali sono le previsioni meteo della settimana",
106
+ "riproduci malibu",
107
+ ],
108
+ "en-US": [
109
+ "turn the lights off please",
110
+ "dim the lights in the hall",
111
+ "clean the flat",
112
+ "cleaning is good dust is so bad do now your magic clean my carpet",
113
+ "list most rated delivery options for chinese food",
114
+ ],
115
+ "es-ES": [
116
+ "apaga las luces por favor",
117
+ "atenua las luces en el pasillo",
118
+ "oscurece la habitación",
119
+ "me gustaría escuchar barcelona de queen",
120
+ "ponme barcelona por queen",
121
+ ],
122
+ "pt-PT": [
123
+ "desligar as luzes",
124
+ "diminuir as luzes no salão",
125
+ "diz qual é o stato da minha memória disponível",
126
+ "mostra uma lista com entrega ao domicílio de comida chinesa com mais avaliações",
127
+ "eu gostava de ouvir punksinatra corridinho à portuguesa",
128
+ ],
129
+ "fr-FR": [
130
+ "éteigne les lumières s'il te plait",
131
+ "tamiser les lumières dans la salle",
132
+ "nettoyer la télévision",
133
+ "trouve mes plats à emporter thaïlandais autour de la concorde",
134
+ "j'aimerais écouter ne me quitte pas de jacques brel",
135
+ ],
136
+ "de-DE": [
137
+ "schalte bitte die lichter aus",
138
+ "dimme die lichter im eingangsbereich",
139
+ "wie lautet der status meines verfügbaren speichers",
140
+ "ich würde gerne queen's barcelona hören",
141
+ "spiel barcelona von queen",
142
+ ],
143
+ }
144
+
145
+ # ================================================================
146
+ # MODEL LOADING
147
+ # ================================================================
148
+ print("🚀 Initializing Echo-DSRN Dashboard...")
149
+ print(f" Base: {BASE_MODEL}")
150
+ print(f" Adapter: {ADAPTER_PATH}")
151
+
152
+ tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True)
153
+ model = AutoModelForCausalLM.from_pretrained(
154
+ BASE_MODEL, torch_dtype=torch.bfloat16, device_map="auto", trust_remote_code=True
155
+ )
156
+ model = PeftModel.from_pretrained(model, ADAPTER_PATH)
157
+ model.eval()
158
+
159
+ DEVICE = next(model.parameters()).device
160
+ print(f" ✅ Model loaded on {DEVICE}")
161
+
162
+ if HAS_WHISPER:
163
+ print("🎙️ Loading Whisper 'tiny' engine...")
164
+ try:
165
+ whisper_model = whisper.load_model("tiny", device=DEVICE)
166
+ print(" ✅ Whisper ready.")
167
+ except Exception as e:
168
+ print(f" ❌ Whisper loading failed: {e}")
169
+ HAS_WHISPER = False
170
+ else:
171
+ print(" ℹ️ Whisper not available (optional dependency).")
172
+ whisper_model = None
173
+
174
+
175
+ # ================================================================
176
+ # OBSERVABILITY HOOKS
177
+ # ================================================================
178
+ def get_dsrn_state_heatmap():
179
+ """Generates a heatmap of the DSRN recurrent state (c_t) magnitude."""
180
+ plt.close('all')
181
+ config = model.config
182
+ state_dim = config.hidden_size * config.num_heads
183
+
184
+ if hasattr(model, "_latest_c_states") and model._latest_c_states is not None:
185
+ c_vector = model._latest_c_states[-1][0].detach().cpu().float().numpy()
186
+ c_vector = np.abs(c_vector)
187
+ else:
188
+ c_vector = np.zeros(state_dim)
189
+
190
+ w = int(math.sqrt(state_dim))
191
+ h = state_dim // w
192
+ state_magnitudes = c_vector[: w * h].reshape((h, w))
193
+
194
+ fig, ax = plt.subplots(figsize=(6, 3.5), dpi=100)
195
+ fig.patch.set_facecolor("#0f0f23")
196
+ ax.set_facecolor("#0f0f23")
197
+
198
+ im = ax.imshow(state_magnitudes, cmap="magma", aspect="auto", interpolation="nearest")
199
+ ax.set_title(
200
+ "DSRN Slow State (c_t) Memory Density", color="#e0e0f0", fontsize=10, fontweight="bold"
201
+ )
202
+ ax.set_xticks([])
203
+ ax.set_yticks([])
204
+
205
+ cbar = plt.colorbar(im, ax=ax, fraction=0.02, pad=0.04)
206
+ cbar.ax.tick_params(colors="#707080", labelsize=7)
207
+ plt.tight_layout()
208
+ return fig
209
+
210
+
211
+ def get_surprise_lambda_visual():
212
+ """Generates a visualization of the Surprise Lambda activation."""
213
+ if hasattr(model, "_latest_gate_stats") and getattr(model, "_latest_gate_stats") is not None:
214
+ surprise_val = model._latest_gate_stats[-1][0, -1].item()
215
+ else:
216
+ surprise_val = 0.0
217
+
218
+ surprise_val = np.clip(surprise_val, 0.0, 1.0)
219
+
220
+ plt.close('all')
221
+ fig, ax = plt.subplots(figsize=(6, 1.0), dpi=100)
222
+ fig.patch.set_facecolor("#0f0f23")
223
+ ax.set_facecolor("#0f0f23")
224
+
225
+ color = '#ef4444' if surprise_val > 0.6 else '#f59e0b' if surprise_val > 0.3 else '#10b981'
226
+ ax.barh([0], [surprise_val], color=color, height=0.6, alpha=0.9, zorder=2)
227
+ ax.barh([0], [1.0], color='white', height=0.6, alpha=0.1, zorder=0)
228
+
229
+ ax.set_xlim(0, 1)
230
+ ax.set_ylim(-0.5, 0.5)
231
+ ax.set_yticks([])
232
+ ax.set_xticks([0, 0.25, 0.5, 0.75, 1.0])
233
+ ax.tick_params(colors="#707080", labelsize=8)
234
+ for spine in ax.spines.values():
235
+ spine.set_visible(False)
236
+
237
+ ax.set_title(
238
+ f"DSRN Surprise Signal (λ_t): {surprise_val:.4f}",
239
+ color="#e0e0f0",
240
+ fontsize=9,
241
+ fontweight="bold",
242
+ )
243
+ plt.tight_layout()
244
+ return fig
245
+
246
+
247
+ # ================================================================
248
+ # INFERENCE LOGIC
249
+ # ================================================================
250
+ def transcribe_audio(audio_path):
251
+ if not HAS_WHISPER or audio_path is None:
252
+ return ""
253
+ try:
254
+ result = whisper_model.transcribe(audio_path)
255
+ return result["text"].strip()
256
+ except Exception as e:
257
+ return f"Error transcribing: {e}"
258
+
259
+
260
+ def classify_intent(utterance, locale):
261
+ if not utterance.strip():
262
+ yield "", None, None, "0.0 TPS"
263
+ return
264
+
265
+ messages = [
266
+ {
267
+ "role": "system",
268
+ "content": "You are a helpful multilingual intent classification assistant.",
269
+ },
270
+ {"role": "user", "content": f"Classify the intent of the following request: {utterance}"},
271
+ ]
272
+
273
+ prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
274
+ inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)
275
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
276
+
277
+ gen_kwargs = {
278
+ "input_ids": inputs.input_ids,
279
+ "max_new_tokens": 15,
280
+ "do_sample": False,
281
+ "pad_token_id": tokenizer.eos_token_id,
282
+ "streamer": streamer,
283
+ "output_dsrn_telemetry": True,
284
+ }
285
+
286
+ thread = Thread(target=lambda: model.generate(**gen_kwargs))
287
+ thread.start()
288
+
289
+ start_time = time.time()
290
+ tokens = 0
291
+ full_response = ""
292
+
293
+ for chunk in streamer:
294
+ full_response += chunk
295
+ tokens += 1
296
+ elapsed = time.time() - start_time
297
+ tps = tokens / elapsed if elapsed > 0 else 0
298
+
299
+ # Plotting is heavy, refresh every few tokens or at the end
300
+ if tokens % 2 == 0:
301
+ yield full_response.strip(), get_dsrn_state_heatmap(), get_surprise_lambda_visual(), f"⚡ {tps:.1f} TPS"
302
+
303
+ # Final yield to ensure plot is up to date
304
+ elapsed = time.time() - start_time
305
+ tps = tokens / elapsed if elapsed > 0 else 0
306
+ yield full_response.strip(), get_dsrn_state_heatmap(), get_surprise_lambda_visual(), f"⚡ {tps:.1f} TPS"
307
+
308
+
309
+ # ================================================================
310
+ # UI CONSTRUCTION
311
+ # ================================================================
312
+ with gr.Blocks(theme=gr.themes.Soft(), title="Echo-DSRN Multilingual Intent Classifier") as demo:
313
+ gr.Markdown(
314
+ f"""
315
+ # 🎙️ Echo Intent: Multilingual DSRN Dashboard
316
+ ### High-Fidelity 1-Shot Inference & Observability Cockpit
317
+
318
+ This dashboard provides real-time intent classification across 60 categories.
319
+
320
+ **🚀 Model Lineage**:
321
+ - **Base**: `{BASE_MODEL}`
322
+ - **Adapter**: `{ADAPTER_PATH}`
323
+
324
+ **⚠️ Limitations**: While highly optimized for edge-routing, accuracy varies by locale. The 114M model may occasionally confuse overlapping semantic clusters (e.g., *calendar* vs. *alarm*) in low-context utterances.
325
+ """
326
+ )
327
+
328
+ with gr.Row():
329
+ with gr.Column(scale=2):
330
+ with gr.Group():
331
+ locale = gr.Dropdown(
332
+ choices=list(EXAMPLES.keys()),
333
+ value="en-US",
334
+ label="Target Locale",
335
+ info="Select target language for intent examples.",
336
+ )
337
+ examples_dropdown = gr.Dropdown(
338
+ choices=EXAMPLES["en-US"],
339
+ value=EXAMPLES["en-US"][0],
340
+ label="Example Utterances",
341
+ info="Pre-compiled samples from the Amazon MASSIVE validation set.",
342
+ )
343
+
344
+ audio_input = gr.Audio(
345
+ sources=["microphone"],
346
+ type="filepath",
347
+ label="Voice Command (Experimental)",
348
+ visible=HAS_WHISPER,
349
+ )
350
+ transcribe_btn = gr.Button(
351
+ "🎤 Transcribe Audio", variant="secondary", visible=HAS_WHISPER
352
+ )
353
+
354
+ input_text = gr.Textbox(
355
+ value=EXAMPLES["en-US"][0],
356
+ placeholder="Enter a request in any supported language...",
357
+ label="Utterance",
358
+ lines=2,
359
+ )
360
+
361
+ with gr.Row():
362
+ classify_btn = gr.Button("🚀 Classify Intent", variant="primary")
363
+ reset_btn = gr.Button("🔄 Reset")
364
+
365
+ with gr.Group():
366
+ gr.Markdown("### 🏷️ Predicted Intent")
367
+ output_label = gr.Label(label="", show_label=False)
368
+ tps_stats = gr.Markdown("**Telemetry:** 0.0 TPS")
369
+
370
+ with gr.Column(scale=3):
371
+ gr.Markdown("### 🧠 DSRN Core Observability")
372
+ surprise_plot = gr.Plot(label="Surprise Bar")
373
+ heatmap_plot = gr.Plot(label="State Heatmap")
374
+
375
+ with gr.Accordion("📚 Reference: Intent Registry (60 Classes)", open=False):
376
+ gr.Markdown(", ".join([f"`{i}`" for i in INTENTS]))
377
+
378
+ # --- EVENT HANDLERS ---
379
+ def update_examples(loc):
380
+ return gr.update(choices=EXAMPLES[loc], value=EXAMPLES[loc][0])
381
+
382
+ def handle_reset():
383
+ return None, "", "en-US", EXAMPLES["en-US"][0], None, None, None, "**Telemetry:** 0.0 TPS"
384
+
385
+ locale.change(update_examples, locale, examples_dropdown)
386
+ examples_dropdown.change(lambda x: x, examples_dropdown, input_text)
387
+
388
+ transcribe_btn.click(transcribe_audio, inputs=[audio_input], outputs=[input_text])
389
+
390
+ classify_btn.click(
391
+ classify_intent,
392
+ inputs=[input_text, locale],
393
+ outputs=[output_label, heatmap_plot, surprise_plot, tps_stats],
394
+ )
395
+
396
+ reset_btn.click(
397
+ handle_reset,
398
+ outputs=[
399
+ audio_input,
400
+ input_text,
401
+ locale,
402
+ examples_dropdown,
403
+ output_label,
404
+ heatmap_plot,
405
+ surprise_plot,
406
+ tps_stats,
407
+ ],
408
+ )
409
+
410
+ if __name__ == "__main__":
411
+ demo.launch(server_name="0.0.0.0", server_port=7860)