Download processing_lfg3.py from glenn2/LFG-3: direct link, hf CLI and curl.
- Browser
- Download file 5.79 kB
-
https://huggingface.co/glenn2/LFG-3/resolve/main/processing_lfg3.py
- Command line
-
hf download hf://glenn2/LFG-3/processing_lfg3.py
-
curl -L -o processing_lfg3.py https://huggingface.co/glenn2/LFG-3/resolve/main/processing_lfg3.py
5.79 kB
| from __future__ import annotations | |
| import numpy as np | |
| import torch | |
| from transformers import Gemma4Processor | |
| from transformers.feature_extraction_utils import BatchFeature | |
| from .parakeet_projector import PARAKEET_NAME, valid_frames_for | |
| TARGET_SR = 16000 | |
| def _load_audio(item, target_sr: int = TARGET_SR): | |
| if isinstance(item, dict): | |
| arr = np.asarray(item.get("array"), dtype="float32") | |
| sr = int(item.get("sampling_rate") or target_sr) | |
| elif isinstance(item, (str, bytes)): | |
| import librosa | |
| arr, sr = librosa.load(item, sr=target_sr, mono=True) | |
| else: | |
| arr, sr = np.asarray(item, dtype="float32"), target_sr | |
| if arr.ndim > 1: | |
| arr = arr.mean(axis=1) | |
| if sr != target_sr: | |
| import librosa | |
| arr = librosa.resample(arr.astype("float32"), orig_sr=sr, | |
| target_sr=target_sr) | |
| return arr.astype("float32") | |
| def _collect_audio(messages): | |
| out = [] | |
| for m in messages: | |
| content = m.get("content") | |
| if not isinstance(content, (list, tuple)): | |
| continue | |
| for part in content: | |
| if isinstance(part, dict) and part.get("type") in ("audio", | |
| "input_audio"): | |
| out.append(part.get("audio", part.get("url"))) | |
| return out | |
| class LFG3Processor(Gemma4Processor): | |
| parakeet_name = PARAKEET_NAME | |
| def _feature_extractor(self): | |
| if getattr(self, "_parakeet_fe", None) is None: | |
| from transformers import AutoFeatureExtractor | |
| self._parakeet_fe = AutoFeatureExtractor.from_pretrained( | |
| self.parakeet_name | |
| ) | |
| return self._parakeet_fe | |
| def _scaffold_ids(self): | |
| tok = self.tokenizer | |
| ids = tuple(tok.convert_tokens_to_ids(t) | |
| for t in ("<|audio|>", "<|audio>", "<audio|>")) | |
| if any(i is None or i == tok.unk_token_id for i in ids): | |
| raise ValueError(f"audio scaffold tokens missing from vocab: {ids}") | |
| return ids | |
| def _batch_features(self, clips): | |
| fe = self._feature_extractor() | |
| per_clip, lengths = [], [] | |
| for clip in clips: | |
| out = fe(clip, sampling_rate=TARGET_SR, return_tensors="pt") | |
| feats = out["input_features"][0] | |
| mask = out.get("attention_mask") | |
| n = int(mask[0].sum()) if mask is not None else feats.shape[0] | |
| per_clip.append(feats[:n]) | |
| lengths.append(n) | |
| width = max(lengths) | |
| padded = torch.zeros((len(per_clip), width, per_clip[0].shape[-1]), | |
| dtype=per_clip[0].dtype) | |
| mask = torch.zeros((len(per_clip), width), dtype=torch.long) | |
| for i, (feats, n) in enumerate(zip(per_clip, lengths)): | |
| padded[i, :n] = feats | |
| mask[i, :n] = 1 | |
| return {"input_features": padded, "encoder_attention_mask": mask} | |
| def _splice(self, text: str, clip) -> tuple[list[int], int]: | |
| ids = self.tokenizer(text, add_special_tokens=False)["input_ids"] | |
| if clip is None: | |
| return ids, 0 | |
| marker_id, boa_id, eoa_id = self._scaffold_ids() | |
| n = valid_frames_for(len(clip), TARGET_SR) | |
| try: | |
| pos = ids.index(marker_id) | |
| except ValueError as exc: | |
| raise ValueError( | |
| "audio supplied but no <|audio|> marker in the rendered " | |
| "prompt; include a {'type': 'audio'} part in the message" | |
| ) from exc | |
| return ids[:pos] + [boa_id] + [marker_id] * n + [eoa_id] + ids[pos + 1:], n | |
| def apply_chat_template( | |
| self, | |
| conversations, | |
| tokenize: bool = True, | |
| return_dict: bool = True, | |
| return_tensors: str = "pt", | |
| padding: bool = False, | |
| add_generation_prompt: bool = True, | |
| enable_thinking: bool = False, | |
| audio=None, | |
| **kwargs, | |
| ): | |
| render = super().apply_chat_template | |
| batched = bool(conversations) and isinstance(conversations[0], | |
| (list, tuple)) | |
| convs = list(conversations) if batched else [conversations] | |
| texts = [render(c, tokenize=False, | |
| add_generation_prompt=add_generation_prompt, | |
| enable_thinking=enable_thinking, **kwargs) | |
| for c in convs] | |
| if not tokenize: | |
| return texts if batched else texts[0] | |
| rows, clips, frames = [], [], [] | |
| for conv, text in zip(convs, texts): | |
| found = _collect_audio(conv) if audio is None else list(audio) | |
| if len(found) > 1: | |
| raise ValueError("one audio clip per conversation is supported; " | |
| f"got {len(found)}") | |
| clip = _load_audio(found[0]) if found else None | |
| ids, n = self._splice(text, clip) | |
| rows.append(ids) | |
| if clip is not None: | |
| clips.append(clip) | |
| frames.append(n) | |
| pad_id = self.tokenizer.pad_token_id or 0 | |
| width = max(len(r) for r in rows) | |
| input_ids = [[pad_id] * (width - len(r)) + r for r in rows] | |
| attn = [[0] * (width - len(r)) + [1] * len(r) for r in rows] | |
| data = { | |
| "input_ids": torch.tensor(input_ids, dtype=torch.long), | |
| "attention_mask": torch.tensor(attn, dtype=torch.long), | |
| } | |
| if clips: | |
| if len(clips) != len(rows): | |
| raise ValueError("every conversation in a batch must carry " | |
| "audio, or none of them may") | |
| data.update(self._batch_features(clips)) | |
| data["valid_frames"] = torch.tensor(frames, dtype=torch.long) | |
| return BatchFeature(data=data) | |