Spaces:
Sleeping
Sleeping
|
Download SIMPLIFIED_DESIGN.md from benhong/blt-patch-view: direct link, hf CLI and curl.
- Browser
- Download file 28.3 kB
-
https://huggingface.co/spaces/benhong/blt-patch-view/resolve/main/SIMPLIFIED_DESIGN.md
- Command line
-
hf download hf://spaces/benhong/blt-patch-view/SIMPLIFIED_DESIGN.md
-
curl -L -o SIMPLIFIED_DESIGN.md https://huggingface.co/spaces/benhong/blt-patch-view/resolve/main/SIMPLIFIED_DESIGN.md
28.3 kB
| # Patch Visualization App - Simplified Design | |
| **Purpose**: Interactive exploration of entropy-based patching strategies on pre-encoded audio | |
| --- | |
| ## Scope (Minimal) | |
| ### What We're Building | |
| - Gradio app to explore **entropy-based patching only** | |
| - Uses **10 pre-selected audio files** from `data/test-10/` | |
| - Uses **single entropy model**: `archive/checkpoints/audio_peoplespeech_50k_scratch/best` | |
| - **Precomputed data**: All entropy/perplexity computed offline | |
| - **Focus**: Parameter exploration, not model inference | |
| ### What We're NOT Building | |
| - β No audio upload | |
| - β No model selection/switching | |
| - β No other patching strategies (strided, space, BPE) | |
| - β No export functionality | |
| - β No batch processing | |
| - β No comparison mode | |
| --- | |
| ## Data Pipeline | |
| ### Step 1: Precomputation Script | |
| **Input**: 10 FLAC files from `data/test-10/` | |
| **Process**: | |
| 1. Load audio with WavTokenizer | |
| 2. Encode to tokens (40 tok/sec, 4096 vocab) | |
| 3. Load entropy model from `archive/checkpoints/audio_peoplespeech_50k_scratch/best` | |
| 4. Compute entropy for each token position | |
| 5. Compute perplexity: `PPL(x_i) = 2^{H(x_i)}` | |
| 6. Generate spectrogram (for visualization) | |
| 7. Save to JSON | |
| **Output**: `data/test-10/precomputed/<file_id>.json` | |
| **JSON Schema**: | |
| ```json | |
| { | |
| "file_id": "1089-134691-0016", | |
| "audio_path": "data/test-10/1089-134691-0016.flac", | |
| "transcript": "...", | |
| "audio_duration_sec": 7.23, | |
| "sample_rate": 24000, | |
| "tokens": [234, 1023, 892, ...], // WavTokenizer tokens | |
| "entropies": [2.34, 1.89, 2.56, ...], // bits | |
| "perplexities": [5.07, 3.71, 5.91, ...] // 2^entropy | |
| } | |
| ``` | |
| **Note**: Spectrogram is computed at runtime from audio file (keeps JSON small) | |
| **Storage Format**: JSON (simple, human-readable, easy to debug) | |
| - Each audio file β 1 JSON file | |
| - Typical size: ~20KB per file (without spectrogram) | |
| - Total: ~200KB for 10 files | |
| --- | |
| ### Step 2: Runtime Patching | |
| **Input**: | |
| - Precomputed JSON | |
| - User parameters (threshold, metric, constraint type) | |
| **Process**: | |
| 1. Load metric array (entropy or perplexity) | |
| 2. Apply patching strategy | |
| 3. Return patch boundaries | |
| **Patching Strategies**: | |
| #### A. Global Threshold | |
| ```python | |
| def patch_global_threshold(metric, threshold): | |
| patches = [] | |
| current_patch = [] | |
| for i, value in enumerate(metric): | |
| current_patch.append(i) | |
| if value > threshold: | |
| patches.append(current_patch) | |
| current_patch = [] | |
| if current_patch: | |
| patches.append(current_patch) | |
| return patches | |
| ``` | |
| #### B. Additive Monotonicity | |
| ```python | |
| def patch_additive_monotonic(metric, threshold): | |
| patches = [] | |
| current_patch = [0] | |
| for i in range(1, len(metric)): | |
| delta = metric[i] - metric[i-1] | |
| if delta > threshold: | |
| patches.append(current_patch) | |
| current_patch = [i] | |
| else: | |
| current_patch.append(i) | |
| if current_patch: | |
| patches.append(current_patch) | |
| return patches | |
| ``` | |
| #### C. Multiplicative Monotonicity | |
| ```python | |
| def patch_multiplicative_monotonic(metric, threshold): | |
| patches = [] | |
| current_patch = [0] | |
| for i in range(1, len(metric)): | |
| if metric[i-1] > 0: # Avoid division by zero | |
| ratio = metric[i] / metric[i-1] | |
| else: | |
| ratio = 1.0 | |
| if ratio > threshold: | |
| patches.append(current_patch) | |
| current_patch = [i] | |
| else: | |
| current_patch.append(i) | |
| if current_patch: | |
| patches.append(current_patch) | |
| return patches | |
| ``` | |
| --- | |
| ## Gradio UI | |
| ### Layout | |
| ``` | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β BLT Patching Visualization Explorer β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β Controls β | |
| ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ€ | |
| β Audio File: β Patching Parameters: β | |
| β [Dropdown βΌ] β Metric: β | |
| β β β Entropy β Perplexity β | |
| β 1089-134691-0016 β β | |
| β Duration: 7.2s β Constraint: β | |
| β Tokens: 289 β β Global Threshold β | |
| β β β Additive Monotonic β | |
| β Transcript: β β Multiplicative Monotonicβ | |
| β "..." (truncated)β β | |
| β β Threshold: [2.0 βββββββ] β | |
| β β (range: 0.0 - 10.0) β | |
| β β β | |
| β β [Apply Patching] β | |
| ββββββββββββββββββββ΄βββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β Waveform + Patch Boundaries β | |
| β [Plotly Interactive Plot] β | |
| β - X-axis: Time (sec) β | |
| β - Y-axis: Amplitude β | |
| β - Red vertical lines: Patch boundaries β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β Spectrogram + Patch Boundaries β | |
| β [Plotly Interactive Plot] β | |
| β - X-axis: Time (sec) β | |
| β - Y-axis: Frequency (Hz) β | |
| β - Color: Magnitude (dB) β | |
| β - Red vertical lines: Patch boundaries β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β Entropy/Perplexity + Patch Boundaries β | |
| β [Plotly Interactive Plot] β | |
| β - X-axis: Token position β | |
| β - Y-axis: Metric value β | |
| β - Blue line: Metric values β | |
| β - Red horizontal line: Threshold β | |
| β - Gray vertical lines: Patch boundaries β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β Patch Statistics β | |
| β Total Patches: 42 β | |
| β Avg Size: 6.9 tokens β | |
| β Min Size: 2 tokens β | |
| β Max Size: 18 tokens β | |
| β β | |
| β [Bar Chart: Patch Size Distribution] β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β Audio Playback β | |
| β [βΆ Play Full Audio] β | |
| β [βΆ Play Until Next Boundary] (from current pos)β | |
| β β | |
| β Current Position: 2.34s (Token 94, Patch 15) β | |
| β Next Boundary: 2.89s (Token 116) β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ``` | |
| ### Interactions | |
| 1. **Select Audio**: Dropdown changes which precomputed data is loaded | |
| 2. **Adjust Parameters**: Sliders/radio buttons update patching in real-time | |
| 3. **Click Plots**: Click on waveform/spectrogram to set playback position | |
| 4. **Play Until Boundary**: Plays from current position until next patch boundary | |
| - Useful for hearing what's in a single patch | |
| - Automatically stops at boundary | |
| --- | |
| ## Implementation | |
| ### File Structure | |
| ``` | |
| apps/patch_view/ | |
| βββ precompute.py # Offline: Compute entropy/perplexity for all samples | |
| βββ app.py # Gradio app | |
| βββ patching.py # Patching strategies | |
| βββ visualization.py # Plotly plot generation | |
| βββ audio.py # Audio playback utilities | |
| βββ README.md # Usage instructions | |
| ``` | |
| ### Dependencies | |
| ```python | |
| # requirements.txt | |
| gradio>=4.0 | |
| plotly>=5.0 | |
| numpy>=1.24 | |
| torch>=2.0 | |
| torchaudio>=2.0 | |
| librosa>=0.10 | |
| soundfile>=0.12 | |
| ``` | |
| ### Core Functions | |
| #### precompute.py | |
| ```python | |
| import torch | |
| import torchaudio | |
| import librosa | |
| import numpy as np | |
| import json | |
| from pathlib import Path | |
| def load_entropy_model(checkpoint_path): | |
| """Load entropy model from checkpoint""" | |
| # Load from archive/checkpoints/audio_peoplespeech_50k_scratch/best | |
| pass | |
| def compute_entropies(audio_path, entropy_model): | |
| """ | |
| Load audio, encode with WavTokenizer, compute entropies | |
| Returns: | |
| tokens: np.array of token IDs | |
| entropies: np.array of entropy values (bits) | |
| """ | |
| pass | |
| def compute_spectrogram(audio, sr=24000): | |
| """ | |
| Generate mel-spectrogram for visualization | |
| Returns: | |
| times: np.array of time points | |
| frequencies: np.array of frequency bins | |
| magnitudes: 2D np.array (freq x time) | |
| """ | |
| S = librosa.feature.melspectrogram( | |
| y=audio, sr=sr, n_mels=128, fmax=8000 | |
| ) | |
| S_db = librosa.power_to_db(S, ref=np.max) | |
| times = librosa.frames_to_time( | |
| np.arange(S.shape[1]), sr=sr, hop_length=512 | |
| ) | |
| frequencies = librosa.mel_frequencies(n_mels=128, fmax=8000) | |
| return times, frequencies, S_db | |
| def precompute_all(): | |
| """Precompute entropy/perplexity for all test-10 samples""" | |
| entropy_model = load_entropy_model( | |
| "archive/checkpoints/audio_peoplespeech_50k_scratch/best" | |
| ) | |
| test_dir = Path("data/test-10") | |
| output_dir = Path("data/test-10/precomputed") | |
| output_dir.mkdir(exist_ok=True) | |
| for audio_file in sorted(test_dir.glob("*.flac")): | |
| print(f"Processing {audio_file.name}...") | |
| # Load audio | |
| audio, sr = librosa.load(audio_file, sr=24000, mono=True) | |
| # Compute tokens and entropies | |
| tokens, entropies = compute_entropies(audio_file, entropy_model) | |
| # Compute perplexities | |
| perplexities = 2 ** entropies | |
| # Compute spectrogram | |
| times, freqs, mags = compute_spectrogram(audio, sr) | |
| # Load transcript | |
| txt_file = audio_file.with_suffix(".txt") | |
| transcript = txt_file.read_text().strip() if txt_file.exists() else "" | |
| # Save | |
| data = { | |
| "file_id": audio_file.stem, | |
| "audio_path": str(audio_file), | |
| "transcript": transcript, | |
| "audio_duration_sec": float(len(audio) / sr), | |
| "sample_rate": int(sr), | |
| "tokens": tokens.tolist(), | |
| "entropies": entropies.tolist(), | |
| "perplexities": perplexities.tolist(), | |
| "spectrogram": { | |
| "times": times.tolist(), | |
| "frequencies": freqs.tolist(), | |
| "magnitudes": mags.tolist() | |
| } | |
| } | |
| output_file = output_dir / f"{audio_file.stem}.json" | |
| with open(output_file, 'w') as f: | |
| json.dump(data, f, indent=2) | |
| print(f" Saved to {output_file}") | |
| if __name__ == "__main__": | |
| precompute_all() | |
| ``` | |
| #### patching.py | |
| ```python | |
| import numpy as np | |
| from typing import List | |
| def patch_global_threshold(metric: np.ndarray, threshold: float) -> List[List[int]]: | |
| """Patch when metric exceeds threshold""" | |
| patches = [] | |
| current_patch = [] | |
| for i, value in enumerate(metric): | |
| current_patch.append(i) | |
| if value > threshold: | |
| patches.append(current_patch) | |
| current_patch = [] | |
| if current_patch: | |
| patches.append(current_patch) | |
| return patches | |
| def patch_additive_monotonic(metric: np.ndarray, threshold: float) -> List[List[int]]: | |
| """Patch when metric increase exceeds threshold""" | |
| patches = [] | |
| current_patch = [0] | |
| for i in range(1, len(metric)): | |
| delta = metric[i] - metric[i-1] | |
| if delta > threshold: | |
| patches.append(current_patch) | |
| current_patch = [i] | |
| else: | |
| current_patch.append(i) | |
| if current_patch: | |
| patches.append(current_patch) | |
| return patches | |
| def patch_multiplicative_monotonic(metric: np.ndarray, threshold: float) -> List[List[int]]: | |
| """Patch when metric ratio exceeds threshold""" | |
| patches = [] | |
| current_patch = [0] | |
| for i in range(1, len(metric)): | |
| if metric[i-1] > 1e-6: # Avoid division by zero | |
| ratio = metric[i] / metric[i-1] | |
| else: | |
| ratio = 1.0 | |
| if ratio > threshold: | |
| patches.append(current_patch) | |
| current_patch = [i] | |
| else: | |
| current_patch.append(i) | |
| if current_patch: | |
| patches.append(current_patch) | |
| return patches | |
| def apply_patching(metric: np.ndarray, | |
| constraint: str, | |
| threshold: float) -> List[List[int]]: | |
| """ | |
| Apply patching strategy | |
| Args: | |
| metric: Entropy or perplexity values | |
| constraint: "global", "additive", or "multiplicative" | |
| threshold: Threshold value | |
| Returns: | |
| List of patches (each patch is list of token indices) | |
| """ | |
| if constraint == "global": | |
| return patch_global_threshold(metric, threshold) | |
| elif constraint == "additive": | |
| return patch_additive_monotonic(metric, threshold) | |
| elif constraint == "multiplicative": | |
| return patch_multiplicative_monotonic(metric, threshold) | |
| else: | |
| raise ValueError(f"Unknown constraint: {constraint}") | |
| def compute_patch_statistics(patches: List[List[int]]) -> dict: | |
| """Compute statistics about patches""" | |
| sizes = [len(p) for p in patches] | |
| return { | |
| "num_patches": len(patches), | |
| "avg_size": np.mean(sizes), | |
| "min_size": np.min(sizes), | |
| "max_size": np.max(sizes), | |
| "median_size": np.median(sizes), | |
| "std_size": np.std(sizes), | |
| "sizes": sizes | |
| } | |
| ``` | |
| #### visualization.py | |
| ```python | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import numpy as np | |
| def create_visualization(data: dict, patches: List[List[int]], | |
| metric_name: str, threshold: float): | |
| """ | |
| Create 4-panel visualization | |
| Args: | |
| data: Precomputed data dict | |
| patches: Patch boundaries | |
| metric_name: "entropy" or "perplexity" | |
| threshold: Threshold value | |
| Returns: | |
| Plotly figure | |
| """ | |
| fig = make_subplots( | |
| rows=4, cols=1, | |
| subplot_titles=( | |
| 'Waveform', | |
| 'Spectrogram', | |
| f'{metric_name.capitalize()} + Patch Boundaries', | |
| 'Patch Size Distribution' | |
| ), | |
| specs=[ | |
| [{"type": "scatter"}], | |
| [{"type": "heatmap"}], | |
| [{"type": "scatter"}], | |
| [{"type": "bar"}] | |
| ], | |
| vertical_spacing=0.08, | |
| row_heights=[0.2, 0.3, 0.3, 0.2] | |
| ) | |
| # Get metric data | |
| metric = np.array(data['entropies'] if metric_name == 'entropy' | |
| else data['perplexities']) | |
| # Calculate patch boundary times | |
| tokens_per_sec = 40 # WavTokenizer rate | |
| boundary_times = [] | |
| for patch in patches: | |
| if patch: | |
| boundary_times.append(patch[0] / tokens_per_sec) | |
| # 1. Waveform (simplified - just show patch boundaries) | |
| duration = data['audio_duration_sec'] | |
| fig.add_trace( | |
| go.Scatter( | |
| x=[0, duration], | |
| y=[0, 0], | |
| mode='lines', | |
| name='Audio', | |
| line=dict(color='lightgray', width=1) | |
| ), | |
| row=1, col=1 | |
| ) | |
| # Add patch boundaries to waveform | |
| for t in boundary_times: | |
| fig.add_vline( | |
| x=t, line_width=1, line_dash="solid", | |
| line_color="red", opacity=0.5, | |
| row=1, col=1 | |
| ) | |
| # 2. Spectrogram | |
| spec = data['spectrogram'] | |
| fig.add_trace( | |
| go.Heatmap( | |
| x=spec['times'], | |
| y=spec['frequencies'], | |
| z=spec['magnitudes'], | |
| colorscale='Viridis', | |
| showscale=False | |
| ), | |
| row=2, col=1 | |
| ) | |
| # Add patch boundaries to spectrogram | |
| for t in boundary_times: | |
| fig.add_vline( | |
| x=t, line_width=1, line_dash="solid", | |
| line_color="red", opacity=0.7, | |
| row=2, col=1 | |
| ) | |
| # 3. Entropy/Perplexity plot | |
| token_positions = np.arange(len(metric)) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=token_positions, | |
| y=metric, | |
| mode='lines', | |
| name=metric_name.capitalize(), | |
| line=dict(color='blue', width=2) | |
| ), | |
| row=3, col=1 | |
| ) | |
| # Add threshold line | |
| fig.add_hline( | |
| y=threshold, line_width=2, line_dash="dash", | |
| line_color="red", opacity=0.7, | |
| annotation_text=f"Threshold: {threshold:.2f}", | |
| row=3, col=1 | |
| ) | |
| # Add patch boundaries | |
| for patch in patches: | |
| if patch: | |
| fig.add_vline( | |
| x=patch[0], line_width=1, line_dash="solid", | |
| line_color="gray", opacity=0.5, | |
| row=3, col=1 | |
| ) | |
| # 4. Patch size distribution | |
| sizes = [len(p) for p in patches] | |
| size_counts = {} | |
| for size in sizes: | |
| size_counts[size] = size_counts.get(size, 0) + 1 | |
| fig.add_trace( | |
| go.Bar( | |
| x=list(size_counts.keys()), | |
| y=list(size_counts.values()), | |
| name='Patch Sizes', | |
| marker_color='steelblue' | |
| ), | |
| row=4, col=1 | |
| ) | |
| # Update layout | |
| fig.update_xaxes(title_text="Time (s)", row=1, col=1) | |
| fig.update_xaxes(title_text="Time (s)", row=2, col=1) | |
| fig.update_yaxes(title_text="Frequency (Hz)", row=2, col=1) | |
| fig.update_xaxes(title_text="Token Position", row=3, col=1) | |
| fig.update_yaxes(title_text=metric_name.capitalize(), row=3, col=1) | |
| fig.update_xaxes(title_text="Patch Size (tokens)", row=4, col=1) | |
| fig.update_yaxes(title_text="Count", row=4, col=1) | |
| fig.update_layout( | |
| height=1200, | |
| showlegend=False, | |
| hovermode='x unified' | |
| ) | |
| return fig | |
| ``` | |
| #### audio.py | |
| ```python | |
| import soundfile as sf | |
| import numpy as np | |
| from pathlib import Path | |
| class AudioPlayer: | |
| """Handle audio playback for patches""" | |
| def __init__(self, audio_path: str, sample_rate: int = 24000): | |
| self.audio, self.sr = sf.read(audio_path) | |
| self.sample_rate = sample_rate | |
| self.current_position = 0 # In samples | |
| def get_full_audio(self): | |
| """Return full audio for playback""" | |
| return self.sr, self.audio | |
| def play_until_boundary(self, patches: List[List[int]], | |
| current_token: int) -> tuple: | |
| """ | |
| Get audio from current position until next patch boundary | |
| Args: | |
| patches: List of patches | |
| current_token: Current token position | |
| Returns: | |
| (sample_rate, audio_segment) | |
| """ | |
| # Find which patch contains current token | |
| current_patch_idx = None | |
| for i, patch in enumerate(patches): | |
| if current_token in patch: | |
| current_patch_idx = i | |
| break | |
| if current_patch_idx is None: | |
| return self.sr, np.array([]) | |
| # Get tokens in current patch from current position onward | |
| current_patch = patches[current_patch_idx] | |
| start_token = current_token | |
| end_token = current_patch[-1] | |
| # Convert to audio samples (40 tokens/sec) | |
| tokens_per_sec = 40 | |
| start_sample = int(start_token / tokens_per_sec * self.sr) | |
| end_sample = int((end_token + 1) / tokens_per_sec * self.sr) | |
| # Extract segment | |
| segment = self.audio[start_sample:end_sample] | |
| return self.sr, segment | |
| ``` | |
| #### app.py | |
| ```python | |
| import gradio as gr | |
| import json | |
| import numpy as np | |
| from pathlib import Path | |
| from patching import apply_patching, compute_patch_statistics | |
| from visualization import create_visualization | |
| from audio import AudioPlayer | |
| # Load all precomputed data | |
| PRECOMPUTED_DIR = Path("data/test-10/precomputed") | |
| precomputed_data = {} | |
| def load_precomputed_data(): | |
| """Load all precomputed JSON files""" | |
| for json_file in PRECOMPUTED_DIR.glob("*.json"): | |
| with open(json_file) as f: | |
| data = json.load(f) | |
| precomputed_data[data['file_id']] = data | |
| def get_audio_choices(): | |
| """Get list of available audio files""" | |
| return sorted(precomputed_data.keys()) | |
| def process_patching(file_id, metric_name, constraint, threshold): | |
| """ | |
| Apply patching and generate visualization | |
| Returns: | |
| figure, stats_text, audio | |
| """ | |
| # Get data | |
| data = precomputed_data[file_id] | |
| # Select metric | |
| metric = np.array(data['entropies'] if metric_name == 'Entropy' | |
| else data['perplexities']) | |
| # Apply patching | |
| patches = apply_patching(metric, constraint.lower(), threshold) | |
| # Compute statistics | |
| stats = compute_patch_statistics(patches) | |
| # Generate visualization | |
| fig = create_visualization(data, patches, metric_name.lower(), threshold) | |
| # Format statistics | |
| stats_text = f""" | |
| **Patch Statistics:** | |
| - Total Patches: {stats['num_patches']} | |
| - Average Size: {stats['avg_size']:.2f} tokens | |
| - Min Size: {stats['min_size']} tokens | |
| - Max Size: {stats['max_size']} tokens | |
| - Median Size: {stats['median_size']:.1f} tokens | |
| - Std Dev: {stats['std_size']:.2f} tokens | |
| """ | |
| # Load audio | |
| audio_path = data['audio_path'] | |
| player = AudioPlayer(audio_path) | |
| sr, audio = player.get_full_audio() | |
| return fig, stats_text, (sr, audio), patches | |
| def play_until_boundary(file_id, current_token, patches): | |
| """Play audio from current token until next boundary""" | |
| data = precomputed_data[file_id] | |
| audio_path = data['audio_path'] | |
| player = AudioPlayer(audio_path) | |
| sr, segment = player.play_until_boundary(patches, current_token) | |
| return (sr, segment) | |
| # Gradio Interface | |
| def create_app(): | |
| load_precomputed_data() | |
| with gr.Blocks(title="BLT Patching Visualization") as app: | |
| gr.Markdown("# BLT Patching Visualization Explorer") | |
| gr.Markdown("Explore entropy-based patching strategies on audio") | |
| with gr.Row(): | |
| # Left column: Controls | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Audio Selection") | |
| audio_dropdown = gr.Dropdown( | |
| choices=get_audio_choices(), | |
| value=get_audio_choices()[0], | |
| label="Audio File" | |
| ) | |
| file_info = gr.Markdown() | |
| gr.Markdown("### Patching Parameters") | |
| metric_radio = gr.Radio( | |
| choices=["Entropy", "Perplexity"], | |
| value="Entropy", | |
| label="Metric" | |
| ) | |
| constraint_radio = gr.Radio( | |
| choices=["Global", "Additive", "Multiplicative"], | |
| value="Global", | |
| label="Constraint Type" | |
| ) | |
| threshold_slider = gr.Slider( | |
| minimum=0.0, | |
| maximum=10.0, | |
| value=2.0, | |
| step=0.1, | |
| label="Threshold" | |
| ) | |
| apply_btn = gr.Button("Apply Patching", variant="primary") | |
| # Visualization area | |
| with gr.Column(scale=2): | |
| plot_output = gr.Plot(label="Visualization") | |
| stats_output = gr.Markdown() | |
| # Audio playback | |
| gr.Markdown("### Audio Playback") | |
| with gr.Row(): | |
| audio_output = gr.Audio(label="Full Audio", type="numpy") | |
| with gr.Row(): | |
| current_token_input = gr.Number( | |
| value=0, | |
| label="Current Token Position", | |
| precision=0 | |
| ) | |
| play_boundary_btn = gr.Button("βΆ Play Until Next Boundary") | |
| boundary_audio = gr.Audio(label="Patch Audio", type="numpy") | |
| # State to store patches | |
| patches_state = gr.State([]) | |
| # Update file info when audio changes | |
| def update_file_info(file_id): | |
| data = precomputed_data[file_id] | |
| info = f""" | |
| **Duration:** {data['audio_duration_sec']:.2f}s | |
| **Tokens:** {len(data['tokens'])} | |
| **Transcript:** {data['transcript'][:100]}... | |
| """ | |
| return info | |
| audio_dropdown.change( | |
| update_file_info, | |
| inputs=[audio_dropdown], | |
| outputs=[file_info] | |
| ) | |
| # Apply patching | |
| apply_btn.click( | |
| process_patching, | |
| inputs=[audio_dropdown, metric_radio, constraint_radio, threshold_slider], | |
| outputs=[plot_output, stats_output, audio_output, patches_state] | |
| ) | |
| # Play until boundary | |
| play_boundary_btn.click( | |
| play_until_boundary, | |
| inputs=[audio_dropdown, current_token_input, patches_state], | |
| outputs=[boundary_audio] | |
| ) | |
| # Initialize | |
| app.load( | |
| update_file_info, | |
| inputs=[audio_dropdown], | |
| outputs=[file_info] | |
| ) | |
| return app | |
| if __name__ == "__main__": | |
| app = create_app() | |
| app.launch(server_name="0.0.0.0", server_port=7860) | |
| ``` | |
| --- | |
| ## Development Steps | |
| ### Step 1: Precomputation (1 day) | |
| 1. β Adapt existing entropy analysis script | |
| 2. β Load entropy model checkpoint | |
| 3. β Process all 10 audio files | |
| 4. β Save JSON files | |
| **Command**: | |
| ```bash | |
| uv run python apps/patch_view/precompute.py | |
| ``` | |
| ### Step 2: Core Patching (0.5 days) | |
| 1. β Implement 3 patching strategies | |
| 2. β Test on sample data | |
| 3. β Validate outputs | |
| ### Step 3: Visualization (1 day) | |
| 1. β Create 4-panel Plotly figure | |
| 2. β Add patch boundaries | |
| 3. β Test interactivity | |
| ### Step 4: Gradio UI (1 day) | |
| 1. β Build basic interface | |
| 2. β Wire up controls | |
| 3. β Add audio playback | |
| ### Step 5: Audio Playback (0.5 days) | |
| 1. β Implement "play until boundary" | |
| 2. β Test timing accuracy | |
| ### Step 6: Polish (0.5 days) | |
| 1. β Fix bugs | |
| 2. β Improve layout | |
| 3. β Add documentation | |
| **Total Time**: ~4 days | |
| --- | |
| ## Success Criteria | |
| ### Functional | |
| - β All 10 audio files precomputed | |
| - β 3 patching strategies working | |
| - β Real-time threshold adjustment | |
| - β Interactive visualizations | |
| - β "Play until boundary" feature | |
| ### Performance | |
| - β <1s latency for parameter changes | |
| - β Smooth plot interactions | |
| - β Accurate audio playback timing | |
| ### Quality | |
| - β Clear visualizations | |
| - β Intuitive controls | |
| - β No crashes on edge cases (threshold=0, etc.) | |
| --- | |
| ## Future Enhancements (Optional) | |
| 1. **Click-to-play**: Click on waveform β play from that position | |
| 2. **Patch highlighting**: Click patch β highlight in all plots | |
| 3. **Comparison view**: Show 2 strategies side-by-side | |
| 4. **Statistics table**: Per-patch details in table | |
| 5. **Download data**: Export patches as JSON | |
| These are nice-to-have but not essential for v1. | |