diff --git a/soup_cli/data/loader.py b/soup_cli/data/loader.py index 7ad8c98..1504a7d 100644 --- a/soup_cli/data/loader.py +++ b/soup_cli/data/loader.py @@ -195,7 +195,7 @@ def _validate_audio_files(data: list[dict], audio_dir: Path) -> list[dict]: audio_path = audio_dir / audio_path # Path traversal protection: resolved path must stay under audio_dir resolved = audio_path.resolve() - if not str(resolved).startswith(str(resolved_base)): + if not resolved.is_relative_to(resolved_base): traversal += 1 continue valid.append({**row, "audio": str(resolved)}) diff --git a/soup_cli/trainer/sft.py b/soup_cli/trainer/sft.py index a83bfa0..506ca09 100644 --- a/soup_cli/trainer/sft.py +++ b/soup_cli/trainer/sft.py @@ -424,7 +424,7 @@ class SFTTrainerWrapper: """Load audio-language model via transformers (Qwen2-Audio, Whisper, etc.).""" from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training from rich.panel import Panel as RichPanel - from transformers import AutoModelForCausalLM, AutoProcessor, BitsAndBytesConfig + from transformers import AutoModel, AutoProcessor, BitsAndBytesConfig console.print( RichPanel( @@ -461,7 +461,9 @@ class SFTTrainerWrapper: if bnb_config: model_kwargs["quantization_config"] = bnb_config - self.model = AutoModelForCausalLM.from_pretrained(cfg.base, **model_kwargs) + # Use AutoModel for audio models — AutoModelForCausalLM doesn't handle + # audio-language architectures (Qwen2-Audio, Whisper, etc.) + self.model = AutoModel.from_pretrained(cfg.base, **model_kwargs) if tcfg.quantization in ("4bit", "8bit"): self.model = prepare_model_for_kbit_training(self.model) @@ -485,6 +487,14 @@ class SFTTrainerWrapper: """Prepare dataset for audio fine-tuning with audio loading.""" from datasets import Dataset + try: + import librosa # noqa: F401 + except ImportError: + raise ImportError( + "librosa is required for audio training. " + "Install with: pip install 'soup-cli[audio]'" + ) + def load_and_format_audio(example): import librosa