pradachan commited on
Commit
e27de76
·
verified ·
1 Parent(s): 6c374b4

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. README.md +2 -2
  2. __pycache__/app.cpython-314.pyc +0 -0
  3. app.py +114 -49
README.md CHANGED
@@ -8,7 +8,7 @@ sdk_version: 5.49.1
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
- hardware: cpu-basic
12
  tags:
13
  - track:backyard
14
  - sponsor:modal
@@ -37,7 +37,7 @@ against 9.4% for the vanilla model.
37
  - **Base:** `openai/whisper-large-v3-turbo`
38
  - **Adapter:** [`pradachan/whisper-large-v3-turbo-disfluency-lora`](https://huggingface.co/pradachan/whisper-large-v3-turbo-disfluency-lora)
39
  - **Trained on:** [Modal](https://modal.com) serverless GPUs (LoRA fine-tuning and the probe/eval harness both ran on Modal).
40
- - **Hardware:** CPU (upgrade to t4-small or ZeroGPU for live transcription)
41
 
42
  The gallery examples are real results from the DisfluencySpeech test set, plus one
43
  honest limitation. Vanilla Whisper already deletes most filler "um/uh" natively, so
 
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
+ suggested_hardware: zero-a10g
12
  tags:
13
  - track:backyard
14
  - sponsor:modal
 
37
  - **Base:** `openai/whisper-large-v3-turbo`
38
  - **Adapter:** [`pradachan/whisper-large-v3-turbo-disfluency-lora`](https://huggingface.co/pradachan/whisper-large-v3-turbo-disfluency-lora)
39
  - **Trained on:** [Modal](https://modal.com) serverless GPUs (LoRA fine-tuning and the probe/eval harness both ran on Modal).
40
+ - **Hardware:** ZeroGPU (`@spaces.GPU`) live transcription runs on an on-demand A10G.
41
 
42
  The gallery examples are real results from the DisfluencySpeech test set, plus one
43
  honest limitation. Vanilla Whisper already deletes most filler "um/uh" natively, so
__pycache__/app.cpython-314.pyc CHANGED
Binary files a/__pycache__/app.cpython-314.pyc and b/__pycache__/app.cpython-314.pyc differ
 
app.py CHANGED
@@ -49,7 +49,14 @@ _MODELS = {}
49
 
50
 
51
  def load_models():
52
- """Load processor + both models once, on first GPU call. Returns a dict."""
 
 
 
 
 
 
 
53
  if _MODELS:
54
  return _MODELS
55
 
@@ -62,21 +69,13 @@ def load_models():
62
 
63
  processor = WhisperProcessor.from_pretrained(BASE)
64
 
65
- vanilla = WhisperForConditionalGeneration.from_pretrained(
66
- BASE, torch_dtype=dtype
67
- ).to(device)
68
- vanilla.eval()
69
-
70
- tuned_base = WhisperForConditionalGeneration.from_pretrained(
71
- BASE, torch_dtype=dtype
72
- )
73
- tuned = PeftModel.from_pretrained(tuned_base, ADAPTER).to(device)
74
- tuned.eval()
75
 
76
  _MODELS.update(
77
  processor=processor,
78
- vanilla=vanilla,
79
- tuned=tuned,
80
  device=device,
81
  dtype=dtype,
82
  )
@@ -186,17 +185,17 @@ def diff_html(vanilla_text, tuned_text):
186
  @spaces.GPU(duration=60)
187
  def transcribe(audio_path):
188
  if not audio_path:
189
- return "", "", '<span class="fw-placeholder-text">Record or upload some speech to begin.</span>'
190
 
191
  m = load_models()
 
192
  audio = _load_audio_16k_mono(audio_path)
193
 
194
- vanilla_text = _decode(
195
- m["vanilla"], m["processor"], audio, m["device"], m["dtype"]
196
- )
197
- tuned_text = _decode(
198
- m["tuned"], m["processor"], audio, m["device"], m["dtype"]
199
- )
200
  return vanilla_text, tuned_text, diff_html(vanilla_text, tuned_text)
201
 
202
 
@@ -237,13 +236,13 @@ html, body, gradio-app, .gradio-container, .gradio-container > .main,
237
  .gradio-container .prose, .gradio-container p, .gradio-container span,
238
  .gradio-container label { color: var(--fw-ink-dark); }
239
 
240
- /* ---- hero ------------------------------------------------------------- */
241
  .fw-hero {
242
  background: var(--fw-card);
243
  border: 3px double var(--fw-royal-gold);
244
  border-radius: 8px;
245
- padding: 3rem 2.5rem;
246
- margin: 0.5rem 0 2.2rem;
247
  box-shadow: 0 4px 20px rgba(158,122,68,0.04);
248
  text-align: center;
249
  }
@@ -251,18 +250,18 @@ html, body, gradio-app, .gradio-container, .gradio-container > .main,
251
  font-family: 'Instrument Serif', serif;
252
  font-style: normal;
253
  text-transform: none;
254
- font-size: 1.25rem;
255
  font-weight: 600;
256
  color: var(--fw-royal-gold);
257
- margin: 0 0 1rem;
258
  }
259
  .fw-hero h1 {
260
  font-family: 'Instrument Serif', Georgia, serif !important;
261
  font-weight: 400 !important;
262
- font-size: 4rem !important;
263
- line-height: 1.05 !important;
264
  color: var(--fw-ink-dark) !important;
265
- margin: 0 0 0.8rem !important;
266
  font-style: normal !important;
267
  }
268
  .fw-hero h1 .fw-accent {
@@ -271,19 +270,50 @@ html, body, gradio-app, .gradio-container, .gradio-container > .main,
271
  }
272
  .fw-lede {
273
  font-family: 'Instrument Serif', Georgia, serif !important;
274
- font-size: 2rem !important;
275
  color: var(--fw-ink-muted) !important;
276
- margin: 0 0 1.2rem !important;
277
  }
278
  .fw-tagline {
279
  font-family: 'Newsreader', Georgia, serif !important;
280
- font-size: 1.15rem !important;
281
- line-height: 1.65 !important;
282
  color: var(--fw-ink-dark) !important;
283
- max-width: 60rem;
284
- margin: 0 auto 2.2rem !important;
285
  }
286
  .fw-tagline strong { color: var(--fw-royal-gold); font-weight: 600; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  .fw-badges { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem; }
288
  .fw-badge {
289
  display: inline-block;
@@ -414,6 +444,15 @@ html, body, gradio-app, .gradio-container, .gradio-container > .main,
414
  font-size: 1.35rem !important;
415
  }
416
 
 
 
 
 
 
 
 
 
 
417
  /* ---- scale audio component fonts so they are not diminished ---- */
418
  .gradio-container .fw-audio,
419
  .gradio-container .fw-audio * {
@@ -425,6 +464,16 @@ html, body, gradio-app, .gradio-container, .gradio-container > .main,
425
  .gradio-container .fw-audio button {
426
  font-size: 1.35rem !important;
427
  }
 
 
 
 
 
 
 
 
 
 
428
 
429
  .fw-placeholder-text {
430
  color: var(--fw-ink-dark) !important;
@@ -484,21 +533,28 @@ footer { display: none !important; }
484
 
485
  HEADER_HTML = """
486
  <div class="fw-hero">
487
- <div class="fw-eyebrow">✻ Apache-2.0 • 100% Offline • Trained on Modal </div>
488
  <h1><span class="fw-accent">fluent</span>Whisper</h1>
489
  <p class="fw-lede">Speak messy. Read clean.</p>
490
- <p class="fw-tagline">Cuts fillers, repeats, and false starts in a <strong>single pass.</strong> <br>
491
- On your laptop. Offline.
492
- </p>
493
- <p class="fw-lede">9.4% → 3.4% WER</p>
494
- <p class="fw-tagline">The only open model that does this</p>
 
 
 
 
 
495
 
496
  <div class="fw-badges">
497
  <span class="fw-badge">whisper-large-v3-turbo</span>
498
  <span class="fw-badge">Disfluency correction</span>
499
- <span class="fw-badge">LoRA Finetuned</span>
500
  <span class="fw-badge gold">Apache-2.0</span>
501
  </div>
 
 
502
  </div>
503
  """
504
 
@@ -565,23 +621,32 @@ def build_demo():
565
  type="filepath",
566
  label="Speak or upload audio",
567
  elem_classes=["fw-audio"],
 
 
 
 
 
 
568
  )
569
  run_btn = gr.Button(
570
  "Transcribe", variant="primary", elem_classes=["fw-btn"]
571
  )
572
 
573
- with gr.Row():
574
- vanilla_out = gr.Textbox(
575
- label="Vanilla Whisper", lines=4, elem_classes=["fw-out"]
576
- )
577
- tuned_out = gr.Textbox(
578
- label="Cleaned (LoRA)", lines=4, elem_classes=["fw-out"]
579
- )
580
  diff_out = gr.HTML(
581
  label="What the LoRA removed",
582
  elem_classes=["fw-diff"],
583
- value='<span class="fw-placeholder-text">Record or upload some speech to begin.</span>',
584
  )
 
 
 
 
 
 
 
 
585
 
586
  run_btn.click(
587
  transcribe,
 
49
 
50
 
51
  def load_models():
52
+ """Load the processor and one base model + LoRA once, on first GPU call.
53
+
54
+ We deliberately load the base weights a *single* time and attach the LoRA
55
+ on top. Vanilla decoding runs inside a ``disable_adapter()`` context, so
56
+ there is only one copy of whisper-large-v3-turbo in memory instead of two.
57
+ That roughly halves VRAM/RAM and cold-start time, which keeps the page from
58
+ locking up while the first recording is processed.
59
+ """
60
  if _MODELS:
61
  return _MODELS
62
 
 
69
 
70
  processor = WhisperProcessor.from_pretrained(BASE)
71
 
72
+ base = WhisperForConditionalGeneration.from_pretrained(BASE, torch_dtype=dtype)
73
+ model = PeftModel.from_pretrained(base, ADAPTER).to(device)
74
+ model.eval()
 
 
 
 
 
 
 
75
 
76
  _MODELS.update(
77
  processor=processor,
78
+ model=model,
 
79
  device=device,
80
  dtype=dtype,
81
  )
 
185
  @spaces.GPU(duration=60)
186
  def transcribe(audio_path):
187
  if not audio_path:
188
+ return "", "", '<span class="fw-placeholder-text">Record or upload some speech, then hit Transcribe.</span>'
189
 
190
  m = load_models()
191
+ model, processor = m["model"], m["processor"]
192
  audio = _load_audio_16k_mono(audio_path)
193
 
194
+ # Vanilla = the same weights with the LoRA switched off, so we never hold a
195
+ # second copy of the base model in memory.
196
+ with model.disable_adapter():
197
+ vanilla_text = _decode(model, processor, audio, m["device"], m["dtype"])
198
+ tuned_text = _decode(model, processor, audio, m["device"], m["dtype"])
 
199
  return vanilla_text, tuned_text, diff_html(vanilla_text, tuned_text)
200
 
201
 
 
236
  .gradio-container .prose, .gradio-container p, .gradio-container span,
237
  .gradio-container label { color: var(--fw-ink-dark); }
238
 
239
+ /* ---- hero — kept compact so the recorder sits within the first screen -- */
240
  .fw-hero {
241
  background: var(--fw-card);
242
  border: 3px double var(--fw-royal-gold);
243
  border-radius: 8px;
244
+ padding: 1.8rem 2.5rem 1.6rem;
245
+ margin: 0.25rem 0 1.4rem;
246
  box-shadow: 0 4px 20px rgba(158,122,68,0.04);
247
  text-align: center;
248
  }
 
250
  font-family: 'Instrument Serif', serif;
251
  font-style: normal;
252
  text-transform: none;
253
+ font-size: 1.15rem;
254
  font-weight: 600;
255
  color: var(--fw-royal-gold);
256
+ margin: 0 0 0.5rem;
257
  }
258
  .fw-hero h1 {
259
  font-family: 'Instrument Serif', Georgia, serif !important;
260
  font-weight: 400 !important;
261
+ font-size: 3.2rem !important;
262
+ line-height: 1.04 !important;
263
  color: var(--fw-ink-dark) !important;
264
+ margin: 0 0 0.3rem !important;
265
  font-style: normal !important;
266
  }
267
  .fw-hero h1 .fw-accent {
 
270
  }
271
  .fw-lede {
272
  font-family: 'Instrument Serif', Georgia, serif !important;
273
+ font-size: 1.55rem !important;
274
  color: var(--fw-ink-muted) !important;
275
+ margin: 0 0 0.6rem !important;
276
  }
277
  .fw-tagline {
278
  font-family: 'Newsreader', Georgia, serif !important;
279
+ font-size: 1.1rem !important;
280
+ line-height: 1.55 !important;
281
  color: var(--fw-ink-dark) !important;
282
+ max-width: 42rem;
283
+ margin: 0 auto 1.1rem !important;
284
  }
285
  .fw-tagline strong { color: var(--fw-royal-gold); font-weight: 600; }
286
+
287
+ /* ---- WER stat — the visual anchor of the hero ------------------------- */
288
+ .fw-wer {
289
+ display: flex;
290
+ align-items: baseline;
291
+ justify-content: center;
292
+ gap: 0.5rem;
293
+ margin: 0.4rem 0 0.2rem;
294
+ }
295
+ .fw-wer-num {
296
+ font-family: 'Instrument Serif', Georgia, serif;
297
+ font-size: 3rem;
298
+ line-height: 1;
299
+ color: var(--fw-ink-muted);
300
+ }
301
+ .fw-wer-good { color: var(--fw-royal-gold); font-weight: 600; }
302
+ .fw-wer-arrow { font-size: 2rem; color: var(--fw-ink-muted); }
303
+ .fw-wer-unit {
304
+ font-family: 'Instrument Serif', Georgia, serif;
305
+ font-size: 1.6rem;
306
+ color: var(--fw-ink-dark);
307
+ letter-spacing: 0.04em;
308
+ }
309
+ .fw-wer-note {
310
+ font-family: 'Newsreader', Georgia, serif !important;
311
+ font-size: 0.95rem !important;
312
+ color: var(--fw-ink-muted) !important;
313
+ margin: 0 auto 1.1rem !important;
314
+ max-width: 38rem;
315
+ }
316
+
317
  .fw-badges { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem; }
318
  .fw-badge {
319
  display: inline-block;
 
444
  font-size: 1.35rem !important;
445
  }
446
 
447
+ /* ---- call-to-action cue pointing at the recorder ---------------------- */
448
+ .fw-cta {
449
+ font-family: 'Instrument Serif', Georgia, serif !important;
450
+ font-size: 1.25rem !important;
451
+ font-weight: 600 !important;
452
+ color: var(--fw-royal-gold) !important;
453
+ margin: 1.2rem 0 0 !important;
454
+ }
455
+
456
  /* ---- scale audio component fonts so they are not diminished ---- */
457
  .gradio-container .fw-audio,
458
  .gradio-container .fw-audio * {
 
464
  .gradio-container .fw-audio button {
465
  font-size: 1.35rem !important;
466
  }
467
+ /* Keep the waveform / playback chrome from spilling out of its box (the
468
+ stray "1x" rate badge overflowing the player). */
469
+ .gradio-container .fw-audio .waveform-container,
470
+ .gradio-container .fw-audio .controls,
471
+ .gradio-container .fw-audio .component-wrapper {
472
+ max-width: 100% !important;
473
+ overflow: hidden !important;
474
+ box-sizing: border-box !important;
475
+ }
476
+ .gradio-container .fw-audio .controls * { line-height: normal !important; }
477
 
478
  .fw-placeholder-text {
479
  color: var(--fw-ink-dark) !important;
 
533
 
534
  HEADER_HTML = """
535
  <div class="fw-hero">
536
+ <div class="fw-eyebrow">✻ Apache-2.0 • Runs offline • Trained on Modal</div>
537
  <h1><span class="fw-accent">fluent</span>Whisper</h1>
538
  <p class="fw-lede">Speak messy. Read clean.</p>
539
+ <p class="fw-tagline">Cuts the fillers, repeats, and false starts in a
540
+ <strong>single local pass</strong> — and shows you exactly what it removed.</p>
541
+
542
+ <div class="fw-wer">
543
+ <span class="fw-wer-num">9.4%</span>
544
+ <span class="fw-wer-arrow">→</span>
545
+ <span class="fw-wer-num fw-wer-good">3.4%</span>
546
+ <span class="fw-wer-unit">WER</span>
547
+ </div>
548
+ <p class="fw-wer-note">vanilla Whisper vs. this adapter on DisfluencySpeech — the only open model that does this</p>
549
 
550
  <div class="fw-badges">
551
  <span class="fw-badge">whisper-large-v3-turbo</span>
552
  <span class="fw-badge">Disfluency correction</span>
553
+ <span class="fw-badge">LoRA finetuned</span>
554
  <span class="fw-badge gold">Apache-2.0</span>
555
  </div>
556
+
557
+ <p class="fw-cta">Record yourself just below and watch it clean up ↓</p>
558
  </div>
559
  """
560
 
 
621
  type="filepath",
622
  label="Speak or upload audio",
623
  elem_classes=["fw-audio"],
624
+ show_share_button=False,
625
+ # The animated live-recording waveform renders continuously on the
626
+ # browser's main thread and is the prime suspect for the "page
627
+ # unresponsive" freeze mid-recording. Turn it off; the static
628
+ # playback waveform still appears once recording stops.
629
+ waveform_options=gr.WaveformOptions(show_recording_waveform=False),
630
  )
631
  run_btn = gr.Button(
632
  "Transcribe", variant="primary", elem_classes=["fw-btn"]
633
  )
634
 
635
+ # The diff is the headline result, so it sits right under the button —
636
+ # no scrolling past empty boxes to see what the model did.
 
 
 
 
 
637
  diff_out = gr.HTML(
638
  label="What the LoRA removed",
639
  elem_classes=["fw-diff"],
640
+ value='<span class="fw-placeholder-text">Record or upload some speech, then hit Transcribe.</span>',
641
  )
642
+ with gr.Accordion("Show raw transcripts", open=False):
643
+ with gr.Row():
644
+ vanilla_out = gr.Textbox(
645
+ label="Vanilla Whisper", lines=4, elem_classes=["fw-out"]
646
+ )
647
+ tuned_out = gr.Textbox(
648
+ label="Cleaned (LoRA)", lines=4, elem_classes=["fw-out"]
649
+ )
650
 
651
  run_btn.click(
652
  transcribe,