shangeth-anyreach commited on
Commit
7260578
·
verified ·
1 Parent(s): 3f8f07a

Upload src/dualturn/config/base_config.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/dualturn/config/base_config.py +275 -0
src/dualturn/config/base_config.py ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration management for turn-taking model training.
3
+
4
+ Provides dataclass-based config with validation and YAML loading.
5
+ """
6
+
7
+ from dataclasses import dataclass, field, asdict
8
+ from pathlib import Path
9
+ from typing import Literal, Optional, List
10
+ import yaml
11
+
12
+
13
+ @dataclass
14
+ class ModelConfig:
15
+ """Model architecture hyperparameters."""
16
+ # Qwen backbone
17
+ qwen_model_name: str = "Qwen/Qwen2.5-0.5B"
18
+
19
+ # LoRA config
20
+ lora_r: int = 16
21
+ lora_alpha: int = 32
22
+ lora_dropout: float = 0.05
23
+ lora_target_modules: List[str] = field(
24
+ default_factory=lambda: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
25
+ )
26
+
27
+ # Audio embeddings
28
+ num_codebooks: int = 8
29
+ codebook_size: int = 2049
30
+ hidden_dim: int = 896
31
+
32
+ # Depth predictor
33
+ depth_dim: int = 512
34
+
35
+ # Backbone type: "qwen" (default LLM) or "lstm" (lightweight baseline)
36
+ backbone_type: str = "qwen"
37
+
38
+ # LSTM backbone config (only used when backbone_type="lstm")
39
+ lstm_hidden_dim: int = 512
40
+ lstm_num_layers: int = 2
41
+ lstm_dropout: float = 0.1
42
+ lstm_bidirectional: bool = True
43
+
44
+ # Transformer backbone config (only used when backbone_type="transformer")
45
+ transformer_num_layers: int = 10
46
+ transformer_num_heads: int = 14
47
+ transformer_ff_dim: int = 3584
48
+ transformer_dropout: float = 0.1
49
+
50
+ # Layer probing: None = last layer (default), "weighted" = learned weighted avg,
51
+ # int = specific layer index (0=embedding, 1-24=transformer layers)
52
+ probe_layer: Optional[str] = None
53
+
54
+ # Random init: if True, initialize Qwen with random weights instead of pretrained
55
+ random_init: bool = False
56
+
57
+ # Input mode: "discrete" (codebook embeddings) or "continuous" (Mimi encoder features)
58
+ input_mode: str = "discrete"
59
+ mimi_feat_dim: int = 512
60
+
61
+ # AudioAdapter (dual-task only)
62
+ use_audio_adapter: bool = False
63
+ audio_adapter_heads: int = 8
64
+ audio_adapter_ff_dim: int = 2048
65
+ audio_adapter_dropout: float = 0.1
66
+
67
+ # Prediction head architecture: "linear" (legacy) or "mlp" (2-layer MLP for sparse tasks)
68
+ head_type: str = "linear"
69
+ head_hidden_dim: int = 256
70
+ head_dropout: float = 0.1
71
+
72
+ # Per-task learned layer attention (ELMo-style, per task)
73
+ per_task_layer_attention: bool = False
74
+
75
+ # Per-codebook output heads in depth predictor (vs shared MLP)
76
+ per_codebook_heads: bool = False
77
+
78
+ # Two-stream backbone (autoresearch best_model): siamese per-channel + cross-channel block.
79
+ # Requires backbone_type="transformer" and input_mode="continuous".
80
+ two_stream_backbone: bool = False
81
+
82
+
83
+ @dataclass
84
+ class DataConfig:
85
+ """Data loading and augmentation hyperparameters."""
86
+ # Paths
87
+ processed_dir: str = "data/otospeech_processed_npy"
88
+ splits_path: str = "data/splits.json"
89
+ metadata_path: str = "data/otospeech_metadata.json"
90
+
91
+ # Windowing
92
+ window_frames: int = 125 # 10s at 12.5Hz
93
+ hop_frames_train: int = 25 # 2s hop for training (overlapping)
94
+ hop_frames_val: int = 125 # 10s hop for val/test (non-overlapping)
95
+
96
+ # Augmentation
97
+ channel_swap_prob: float = 0.5 # Channel swap augmentation (train only)
98
+
99
+ # Soft labels
100
+ soft_labels: bool = True
101
+ sigma_before: float = 3.0 # frames (240ms at 12.5Hz)
102
+ sigma_after: float = 1.0 # frames (80ms at 12.5Hz)
103
+
104
+ # Balanced sampling
105
+ balanced_sampling: bool = True
106
+ shift_oversample_ratio: float = 3.0 # Oversample windows with shifts
107
+ dynamic_window_sampling: bool = False # Random window positions around shifts (Stage 3)
108
+
109
+ # Event label widening: dilate sparse labels to N frames (1 = no widening)
110
+ event_label_width: int = 1
111
+
112
+ # Dual-task data paths (SWB + Oto separate)
113
+ swb_processed_dir: Optional[str] = None
114
+ swb_splits_path: Optional[str] = None
115
+ oto_processed_dir: Optional[str] = None
116
+ oto_splits_path: Optional[str] = None
117
+ oto_metadata_path: Optional[str] = None
118
+ max_text_tokens: int = 512
119
+
120
+ # S2S dataset (production call recordings) — included as extra dataset alongside Oto/SWB
121
+ s2s_processed_dir: Optional[str] = None
122
+ s2s_splits_path: Optional[str] = None
123
+
124
+
125
+ @dataclass
126
+ class TrainingConfig:
127
+ """Training loop hyperparameters."""
128
+ # Stage
129
+ stage: Literal["stage2", "stage2_dual_task", "stage3", "linear_probe", "full_finetune", "lstm_baseline"] = "stage2"
130
+
131
+ # Optimization
132
+ learning_rate: float = 1e-4
133
+ weight_decay: float = 0.01
134
+ adam_beta1: float = 0.9
135
+ adam_beta2: float = 0.999
136
+ adam_eps: float = 1e-8
137
+ max_grad_norm: float = 1.0 # Gradient clipping
138
+
139
+ # Scheduler
140
+ warmup_steps: int = 1000
141
+ scheduler_type: str = "cosine" # "cosine", "linear", or "constant"
142
+
143
+ # Training loop
144
+ batch_size: int = 16
145
+ num_epochs: int = 5
146
+ max_steps: Optional[int] = None # If set, overrides num_epochs
147
+ gradient_accumulation_steps: int = 1
148
+
149
+ # Loss weights -- context-aware tasks (v3)
150
+ weight_eot: float = 1.0 # End of Turn
151
+ weight_hold: float = 1.0 # Turn Hold / Pause
152
+ weight_bot: float = 1.0 # Beginning of Turn
153
+ weight_bc: float = 1.0 # Backchannel
154
+ weight_vad: float = 1.0 # Voice Activity Detection (BCE)
155
+ weight_codebook: float = 0.0 # Codebook prediction loss weight
156
+
157
+ # Sparse event loss type: "focal" (recommended) or "wbce" (weighted BCE)
158
+ # Focal loss avoids gradient spikes from high pos_weight by down-weighting
159
+ # easy negatives adaptively. Use "wbce" only for backward compat.
160
+ sparse_loss_type: str = "focal"
161
+
162
+ # Focal loss per-task alpha (positive class weight, in [0,1]).
163
+ # Higher alpha = more weight on positives. Combined with gamma, this is
164
+ # much gentler than wBCE pos_weight while achieving better gradient balance.
165
+ eot_alpha: float = 0.75
166
+ hold_alpha: float = 0.60
167
+ bot_alpha: float = 0.80
168
+ bc_alpha: float = 0.80
169
+ focal_gamma_sparse: float = 2.0
170
+
171
+ # Per-task pos_weight for wBCE (only used when sparse_loss_type="wbce").
172
+ # Derived from combined dataset positive rates after 5-frame widening + VAD mask:
173
+ # EOT~2.7%, HOLD~5.6%, BOT~1.3%, BC~1.4%
174
+ eot_pos_weight: float = 37.0
175
+ hold_pos_weight: float = 17.0
176
+ bot_pos_weight: float = 75.0
177
+ bc_pos_weight: float = 73.0
178
+ sparse_pos_weight: float = 20.0 # legacy fallback if per-task not set in config
179
+
180
+ # Legacy weights (backward compat -- set to 0.0 for new training)
181
+ weight_shift: float = 0.0
182
+ weight_end: float = 0.0
183
+ weight_start: float = 0.0
184
+ focal_gamma: float = 2.0
185
+ focal_alpha: float = 0.75
186
+
187
+ # Future VAD projection -- VAP-style binned voice activity prediction
188
+ weight_fvad: float = 0.0 # Future VAD projection loss weight (0 = disabled)
189
+ fvad_bins: List[int] = field(default_factory=lambda: [3, 6, 12, 25])
190
+ # Bin edges in frames at 12.5Hz: [3,6,12,25] ->
191
+ # bin0: t+1..t+3 (80-240ms), bin1: t+4..t+6 (320-480ms)
192
+ # bin2: t+7..t+12 (560-960ms), bin3: t+13..t+25 (1.04-2.0s)
193
+
194
+ # Text loss (dual-task)
195
+ weight_text: float = 0.0 # ASR text prediction loss weight (Stage 2 dual-task)
196
+ mode: str = "codebook" # forward mode: "codebook", "dual_task", "shift"
197
+
198
+ # Full finetune: merge LoRA into base and unfreeze all params
199
+ full_finetune: bool = False
200
+
201
+ # Per-codebook weighting: [code0, ..., code7]. Default equal.
202
+ # Moshi-inspired: code0 carries prosody -> weight higher.
203
+ codebook_weights: Optional[List[float]] = None
204
+
205
+ # Future VAD auxiliary task (legacy, kept for old config compat)
206
+ vad_lookahead_frames: int = 3
207
+
208
+ # Label mode
209
+ label_mode: str = "start_end" # "shift" (legacy) or "start_end" (v2)
210
+
211
+ # Validation & checkpointing
212
+ eval_every_steps: int = 500
213
+ save_every_steps: int = 1000
214
+ early_stopping_patience: int = 5 # Stop after N evals without improvement
215
+ max_val_batches: Optional[int] = None # Limit val batches (None = full val set; useful for quick tests)
216
+ save_generated_audio: bool = False # Save autoregressive audio samples each validation (Stage-1)
217
+ gen_context_frames: int = 125 # Context frames for generation (125 = 10s at 12.5Hz)
218
+ gen_gen_frames: int = 62 # Frames to generate (62 = ~5s at 12.5Hz)
219
+
220
+ # System
221
+ num_workers: int = 4
222
+ pin_memory: bool = True
223
+ mixed_precision: bool = False # bf16 training (A100 native support)
224
+ seed: int = 42
225
+
226
+ # Logging
227
+ log_every_steps: int = 10
228
+ log_perplexity: bool = True # Log perplexity for codebook prediction (Stage 2)
229
+ wandb_project: Optional[str] = None # "turn-taking-interspeech"
230
+ wandb_run_name: Optional[str] = None
231
+ experiment_name: str = "default"
232
+
233
+ # Paths
234
+ checkpoint_dir: str = "checkpoints"
235
+ log_dir: str = "logs"
236
+ stage2_checkpoint: Optional[str] = None # Path to Stage 2 checkpoint (for Stage 3)
237
+
238
+
239
+ @dataclass
240
+ class ExperimentConfig:
241
+ """Complete experiment configuration."""
242
+ model: ModelConfig = field(default_factory=ModelConfig)
243
+ data: DataConfig = field(default_factory=DataConfig)
244
+ training: TrainingConfig = field(default_factory=TrainingConfig)
245
+
246
+ @classmethod
247
+ def from_yaml(cls, path: str) -> "ExperimentConfig":
248
+ """Load config from YAML file."""
249
+ with open(path, 'r') as f:
250
+ data = yaml.safe_load(f)
251
+
252
+ config = cls(
253
+ model=ModelConfig(**data.get("model", {})),
254
+ data=DataConfig(**data.get("data", {})),
255
+ training=TrainingConfig(**data.get("training", {})),
256
+ )
257
+ return config
258
+
259
+ def to_yaml(self, path: str):
260
+ """Save config to YAML file."""
261
+ data = {
262
+ "model": asdict(self.model),
263
+ "data": asdict(self.data),
264
+ "training": asdict(self.training),
265
+ }
266
+ with open(path, 'w') as f:
267
+ yaml.dump(data, f, default_flow_style=False, sort_keys=False)
268
+
269
+ def to_dict(self) -> dict:
270
+ """Convert to dictionary."""
271
+ return {
272
+ "model": asdict(self.model),
273
+ "data": asdict(self.data),
274
+ "training": asdict(self.training),
275
+ }