Skip to content

Commit e811ad9

Browse files
authored
Longer transcription supporting changes (#1925)
1 parent a1df2df commit e811ad9

File tree

6 files changed

+175
-105
lines changed

6 files changed

+175
-105
lines changed

education-ai-suite/smart-classroom/components/asr_component.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,6 @@ def process(self, input_generator):
187187
if os.path.exists(chunk_path) and DELETE_CHUNK_AFTER_USE:
188188
os.remove(chunk_path)
189189

190-
StorageManager.save_async(transcript_path, transcribed_text, append=True)
191-
192190
yield {
193191
**chunk_data,
194192
"text": transcribed_text,
@@ -208,8 +206,9 @@ def process(self, input_generator):
208206
teacher_speaker = max(self.speaker_text_len, key=self.speaker_text_len.get)
209207

210208
if teacher_speaker:
211-
teacher_lines_with_time = []
209+
teacher_lines = []
212210
full_updated_lines = []
211+
full_timestamped_lines = []
213212

214213
for seg in self.all_segments:
215214
spk = seg["speaker"]
@@ -219,8 +218,8 @@ def process(self, input_generator):
219218

220219
if spk == teacher_speaker:
221220
speaker_label = LABEL_TEACHER
222-
teacher_lines_with_time.append(
223-
f"[{start} - {end}] {speaker_label}: {text}"
221+
teacher_lines.append(
222+
f"{text}"
224223
)
225224
else:
226225
if spk.startswith(f"{LABEL_SPEAKER}_"):
@@ -233,7 +232,11 @@ def process(self, input_generator):
233232
speaker_label = spk
234233

235234
full_updated_lines.append(
236-
f"[{start} - {end}] {speaker_label}: {text}"
235+
f"{speaker_label}: {text}"
236+
)
237+
238+
full_timestamped_lines.append(
239+
f"[{start} - {end}]: {text}"
237240
)
238241

239242
StorageManager.save(
@@ -242,9 +245,15 @@ def process(self, input_generator):
242245
append=False
243246
)
244247

248+
StorageManager.save(
249+
os.path.join(project_path, "content_segmentation_transcription.txt"),
250+
"\n".join(full_timestamped_lines) + "\n",
251+
append=False
252+
)
253+
245254
StorageManager.save(
246255
os.path.join(project_path, "teacher_transcription.txt"),
247-
"\n".join(teacher_lines_with_time) + "\n",
256+
"\n".join(teacher_lines) + "\n",
248257
append=False
249258
)
250259

@@ -269,4 +278,4 @@ def process(self, input_generator):
269278
}
270279
)
271280

272-
logger.info(f"Transcription Complete: {self.session_id}")
281+
logger.info(f"Transcription Complete: {self.session_id}")
Lines changed: 100 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,135 @@
11
from components.llm.base_summarizer import BaseSummarizer
2-
import logging
2+
import logging, threading, gc
33
from transformers import AutoTokenizer, TextIteratorStreamer
44
from optimum.intel.openvino import OVModelForCausalLM
55
from utils import ensure_model
66
from utils.config_loader import config
77
from utils.locks import audio_pipeline_lock
8-
import threading
98

109
logger = logging.getLogger(__name__)
1110

1211

1312
class Summarizer(BaseSummarizer):
1413
def __init__(self, model_name, device, temperature=0.7, revision=None):
1514
self.model_name = model_name
16-
self.device = device.upper() # OpenVINO uses "GPU" or "CPU"
15+
self.device = device.upper()
1716
self.temperature = temperature
1817

19-
model_path = ensure_model.get_model_path()
20-
logger.info(f"Loading Model: model name={self.model_name}, model path={model_path}, device={self.device}")
21-
18+
self.model_path = ensure_model.get_model_path()
19+
20+
logger.info(
21+
f"Summarizer initialized (lazy load). "
22+
f"model={self.model_name}, path={self.model_path}, device={self.device}"
23+
)
24+
2225
self.tokenizer = AutoTokenizer.from_pretrained(
23-
model_path,
24-
trust_remote_code=True,
25-
fix_mistral_regex=True
26+
self.model_path,
27+
trust_remote_code=True,
28+
fix_mistral_regex=True,
2629
)
2730

2831
if self.tokenizer.pad_token is None:
2932
self.tokenizer.pad_token = self.tokenizer.eos_token
30-
31-
self.model = OVModelForCausalLM.from_pretrained(
32-
model_path,
33-
device=self.device,
34-
use_cache=True
33+
34+
def _load_model(self):
35+
logger.info("Loading OVModelForCausalLM instance...")
36+
return OVModelForCausalLM.from_pretrained(
37+
self.model_path,
38+
device=self.device,
39+
use_cache=True,
3540
)
3641

42+
def _destroy_model(self, model):
43+
try:
44+
del model
45+
gc.collect()
46+
logger.info("OV model instance destroyed")
47+
except Exception as e:
48+
logger.warning(f"Failed to destroy OV model cleanly: {e}")
49+
3750
def generate(self, prompt: str, stream: bool = True):
3851
max_new_tokens = config.models.summarizer.max_new_tokens
3952
inputs = self.tokenizer(prompt, return_tensors="pt")
4053

4154
if stream:
4255
class CountingTextIteratorStreamer(TextIteratorStreamer):
43-
def __init__(self, tokenizer, skip_special_tokens=True, skip_prompt=True):
44-
super().__init__(tokenizer, skip_special_tokens=skip_special_tokens, skip_prompt=skip_prompt)
45-
self.total_tokens = 0
56+
def __init__(self, tokenizer, skip_special_tokens=True, skip_prompt=True):
57+
super().__init__(
58+
tokenizer,
59+
skip_special_tokens=skip_special_tokens,
60+
skip_prompt=skip_prompt,
61+
)
62+
self.total_tokens = 0
4663

47-
def put(self, value):
48-
if value is not None:
49-
self.total_tokens += 1
50-
super().put(value)
64+
def put(self, value):
65+
if value is not None:
66+
self.total_tokens += 1
67+
super().put(value)
5168

5269
streamer = CountingTextIteratorStreamer(
53-
self.tokenizer,
54-
skip_special_tokens=True,
55-
skip_prompt=True
70+
self.tokenizer,
71+
skip_special_tokens=True,
72+
skip_prompt=True,
5673
)
57-
74+
5875
def run_generation():
59-
with audio_pipeline_lock:
60-
generation_kwargs = {
61-
"input_ids": inputs.input_ids,
62-
"max_new_tokens": max_new_tokens,
63-
64-
# 🔑 sampling safety
65-
"do_sample": True,
66-
"temperature": max(self.temperature, 0.1),
67-
"top_p": 0.9,
68-
"top_k": 50,
69-
70-
# tokens
71-
"pad_token_id": self.tokenizer.eos_token_id,
72-
"eos_token_id": self.tokenizer.eos_token_id,
73-
74-
# streaming
75-
"streamer": streamer,
76-
}
77-
self.model.generate(**generation_kwargs)
78-
79-
thread = threading.Thread(target=run_generation, daemon=True)
80-
thread.start()
81-
76+
model = None
77+
try:
78+
with audio_pipeline_lock:
79+
model = self._load_model()
80+
model.generate(
81+
input_ids=inputs.input_ids,
82+
max_new_tokens=max_new_tokens,
83+
84+
# sampling
85+
do_sample=True,
86+
temperature=max(self.temperature, 0.1),
87+
top_p=0.9,
88+
top_k=50,
89+
90+
# tokens
91+
pad_token_id=self.tokenizer.eos_token_id,
92+
eos_token_id=self.tokenizer.eos_token_id,
93+
94+
# streaming
95+
streamer=streamer,
96+
)
97+
98+
except Exception:
99+
logger.error(
100+
"Exception occurred in OV streaming generation",
101+
exc_info=True,
102+
)
103+
if hasattr(streamer, "_queue"):
104+
streamer._queue.put(
105+
"[ERROR]: Summary generation failed due to resource constraints."
106+
)
107+
108+
finally:
109+
if model is not None:
110+
self._destroy_model(model)
111+
streamer.end()
112+
113+
threading.Thread(target=run_generation, daemon=True).start()
82114
return streamer
115+
83116
else:
84-
with audio_pipeline_lock:
85-
generation_kwargs = {
86-
"input_ids": inputs.input_ids,
87-
"max_new_tokens": max_new_tokens,
88-
89-
# 🔑 sampling safety
90-
"do_sample": True,
91-
"temperature": max(self.temperature, 0.1),
92-
"top_p": 0.9,
93-
"top_k": 50,
94-
95-
# tokens
96-
"pad_token_id": self.tokenizer.eos_token_id,
97-
"eos_token_id": self.tokenizer.eos_token_id,
98-
}
99-
return self.model.generate(**generation_kwargs)
117+
model = None
118+
try:
119+
with audio_pipeline_lock:
120+
model = self._load_model()
121+
return model.generate(
122+
input_ids=inputs.input_ids,
123+
max_new_tokens=max_new_tokens,
124+
125+
do_sample=True,
126+
temperature=max(self.temperature, 0.1),
127+
top_p=0.9,
128+
top_k=50,
129+
130+
pad_token_id=self.tokenizer.eos_token_id,
131+
eos_token_id=self.tokenizer.eos_token_id,
132+
)
133+
finally:
134+
if model is not None:
135+
self._destroy_model(model)
Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from components.llm.base_summarizer import BaseSummarizer
22
import openvino_genai as ov_genai
33
from transformers import AutoTokenizer
4-
import logging, threading
4+
import logging, threading, gc
55
from utils import ensure_model
66
from utils.config_loader import config
77
from utils.ov_genai_util import YieldingTextStreamer
@@ -15,21 +15,27 @@ def __init__(self, model_name, device, temperature=0.7, revision=None):
1515
self.temperature = temperature
1616
logger.info(f"Loading Model: model name={self.model_name}, model path={ensure_model.get_model_path()}, device={self.device}")
1717
self.tokenizer = AutoTokenizer.from_pretrained(ensure_model.get_model_path())
18-
self.model = ov_genai.LLMPipeline(ensure_model.get_model_path(), device=device)
1918

2019
def generate(self, prompt, stream: bool = True):
2120
if stream:
2221
streamer = YieldingTextStreamer(self.tokenizer)
2322

2423
def run_generation():
24+
model = None
2525
try:
26-
audio_pipeline_lock.acquire()
27-
self.model.generate(
28-
prompt,
29-
streamer=streamer,
30-
max_new_tokens=config.models.summarizer.max_new_tokens,
31-
temperature=self.temperature,
32-
)
26+
with audio_pipeline_lock:
27+
model = self._load_model()
28+
model.generate(
29+
prompt,
30+
streamer=streamer,
31+
max_new_tokens=config.models.summarizer.max_new_tokens,
32+
temperature=self.temperature,
33+
do_sample=False,
34+
)
35+
cfg = model.get_generation_config()
36+
for attr in dir(cfg):
37+
if not attr.startswith("_"):
38+
logger.info(f" {attr}: {getattr(cfg, attr)}")
3339

3440
except Exception as e:
3541
error_msg = "Summary generation failed. Please ensure sufficient free resources are available to run this process."
@@ -38,15 +44,35 @@ def run_generation():
3844
error_msg = "Summary generation failed. Insufficient GPU resources available to run this process."
3945
streamer._queue.put(f"[ERROR]: {error_msg}")
4046
finally:
41-
audio_pipeline_lock.release()
47+
if model is not None:
48+
self._destroy_model(model)
4249
streamer.end()
4350

4451
threading.Thread(target=run_generation, daemon=True).start()
4552
return streamer
4653
else:
47-
with audio_pipeline_lock:
48-
return self.model.generate(
54+
model = None
55+
try:
56+
with audio_pipeline_lock:
57+
model = self._load_model()
58+
return model.generate(
4959
prompt,
5060
max_new_tokens=config.models.summarizer.max_new_tokens,
5161
temperature=self.temperature,
62+
do_sample=False,
5263
)
64+
finally:
65+
if model is not None:
66+
self._destroy_model(model)
67+
68+
def _load_model(self):
69+
logger.info("Loading model instance...")
70+
return ov_genai.LLMPipeline(ensure_model.get_model_path(), device=self.device)
71+
72+
def _destroy_model(self, model):
73+
try:
74+
del model
75+
gc.collect()
76+
logger.info("Model instance destroyed and memory reclaimed")
77+
except Exception as e:
78+
logger.warning(f"Failed to fully destroy model: {e}")

education-ai-suite/smart-classroom/components/summarizer_component.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,24 @@ def __init__(self, session_id, provider, model_name, device, temperature=0.7, mo
2323
provider = provider.lower()
2424
cfg = (provider, model_name, device)
2525

26-
# Reload only if config changed
27-
if SummarizerComponent._model is None or SummarizerComponent._config != cfg:
28-
if provider == "openvino":
29-
SummarizerComponent._model = OvSummarizer(
30-
model_name=model_name,
31-
device=device,
32-
temperature=temperature,
33-
revision=None
34-
)
35-
elif provider == "ipex":
36-
SummarizerComponent._model = IpexSummarizer(
37-
model_name=model_name,
38-
device=device.lower(),
39-
temperature=temperature
40-
)
41-
else:
42-
raise ValueError(f"Unsupported summarizer provider: {provider}")
43-
44-
SummarizerComponent._config = cfg
26+
27+
if provider == "openvino":
28+
SummarizerComponent._model = OvSummarizer(
29+
model_name=model_name,
30+
device=device,
31+
temperature=temperature,
32+
revision=None
33+
)
34+
elif provider == "ipex":
35+
SummarizerComponent._model = IpexSummarizer(
36+
model_name=model_name,
37+
device=device.lower(),
38+
temperature=temperature
39+
)
40+
else:
41+
raise ValueError(f"Unsupported summarizer provider: {provider}")
42+
43+
SummarizerComponent._config = cfg
4544

4645
self.summarizer = SummarizerComponent._model
4746
self.model_name = model_name

0 commit comments

Comments
 (0)