Download src/s06_generator.py from krishnah27/AriaLM: direct link, hf CLI and curl.
- Browser
- Download file 4.59 kB
-
https://huggingface.co/krishnah27/AriaLM/resolve/main/src/s06_generator.py
- Command line
-
hf download hf://krishnah27/AriaLM/src/s06_generator.py
-
curl -L -o s06_generator.py https://huggingface.co/krishnah27/AriaLM/resolve/main/src/s06_generator.py
4.59 kB
| """ | |
| Music generation / inference with: | |
| - Top-k / Top-p (nucleus) sampling | |
| - Temperature scaling | |
| - Repetition penalty | |
| - KV-cache for efficient autoregressive generation | |
| """ | |
| import logging | |
| from pathlib import Path | |
| import torch | |
| import torch.nn.functional as F | |
| from src.s01_config import GenConfig, get_device | |
| from src.s02_tokenizer import MusicTokenizer, BOS_TOKEN, EOS_TOKEN | |
| from src.s04_model import MusicTransformer | |
| logger = logging.getLogger(__name__) | |
| def top_k_top_p_filter(logits: torch.Tensor, top_k: int, top_p: float) -> torch.Tensor: | |
| """Filter logits using top-k and nucleus (top-p) sampling.""" | |
| if top_k > 0: | |
| top_k = min(top_k, logits.size(-1)) | |
| indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None] | |
| logits[indices_to_remove] = float("-inf") | |
| if top_p < 1.0: | |
| sorted_logits, sorted_indices = torch.sort(logits, descending=True) | |
| cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) | |
| sorted_indices_to_remove = cumulative_probs > top_p | |
| sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone() | |
| sorted_indices_to_remove[..., 0] = 0 | |
| indices_to_remove = sorted_indices_to_remove.scatter( | |
| dim=-1, index=sorted_indices, src=sorted_indices_to_remove | |
| ) | |
| logits[indices_to_remove] = float("-inf") | |
| return logits | |
| def apply_repetition_penalty(logits: torch.Tensor, past_tokens: list[int], penalty: float): | |
| """Penalize tokens that have appeared recently.""" | |
| if penalty == 1.0 or not past_tokens: | |
| return logits | |
| # Only penalize last 64 tokens to avoid over-suppression | |
| recent = past_tokens[-64:] | |
| unique_tokens = set(recent) | |
| for token_id in unique_tokens: | |
| if logits[0, token_id] > 0: | |
| logits[0, token_id] /= penalty | |
| else: | |
| logits[0, token_id] *= penalty | |
| return logits | |
| def generate( | |
| model: MusicTransformer, | |
| tokenizer: MusicTokenizer, | |
| config: GenConfig, | |
| prompt_tokens: list[int] | None = None, | |
| device: torch.device | None = None, | |
| ) -> list[int]: | |
| """ | |
| Generate music tokens autoregressively using KV-cache. | |
| Args: | |
| model: Trained MusicTransformer | |
| tokenizer: MusicTokenizer instance | |
| config: Generation config (temperature, top_k, etc.) | |
| prompt_tokens: Optional seed tokens (if None, starts with BOS) | |
| device: Target device | |
| Returns: | |
| List of generated token IDs | |
| """ | |
| if device is None: | |
| device = get_device() | |
| model.eval() | |
| model.reset_caches() | |
| model.to(device) | |
| if prompt_tokens is None: | |
| prompt_tokens = [BOS_TOKEN] | |
| generated = list(prompt_tokens) | |
| # Set seed for reproducibility | |
| if config.seed is not None: | |
| torch.manual_seed(config.seed) | |
| # Process prompt in one shot (prefill) | |
| input_tensor = torch.tensor([generated], dtype=torch.long, device=device) | |
| logits, _ = model(input_tensor, use_cache=True) | |
| for step in range(config.max_tokens - len(generated)): | |
| # Get logits for last position | |
| next_logits = logits[:, -1, :] / max(config.temperature, 1e-8) | |
| # Apply repetition penalty | |
| next_logits = apply_repetition_penalty( | |
| next_logits, generated, config.repetition_penalty | |
| ) | |
| # Top-k / Top-p filtering | |
| next_logits = top_k_top_p_filter(next_logits, config.top_k, config.top_p) | |
| # Sample | |
| probs = F.softmax(next_logits, dim=-1) | |
| next_token = torch.multinomial(probs, num_samples=1).item() | |
| generated.append(next_token) | |
| # Stop on EOS | |
| if next_token == EOS_TOKEN: | |
| break | |
| # Next step: single token with KV-cache | |
| input_tensor = torch.tensor([[next_token]], dtype=torch.long, device=device) | |
| logits, _ = model(input_tensor, use_cache=True) | |
| model.reset_caches() | |
| return generated | |
| def generate_midi_file( | |
| model: MusicTransformer, | |
| tokenizer: MusicTokenizer, | |
| config: GenConfig, | |
| output_path: Path, | |
| prompt_tokens: list[int] | None = None, | |
| ): | |
| """Generate a MIDI file and save to disk.""" | |
| logger.info(f"Generating music (max_tokens={config.max_tokens}, temp={config.temperature})...") | |
| tokens = generate(model, tokenizer, config, prompt_tokens) | |
| logger.info(f"Generated {len(tokens)} tokens") | |
| midi = tokenizer.tokens_to_midi(tokens) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| midi.write(str(output_path)) | |
| logger.info(f"Saved MIDI to {output_path}") | |
| return tokens | |