WaveCut's picture
Organize runtime and kernel code under src
44f9814 verified
Raw History Blame Contribute Delete
6.06 kB
"""Versioned, checkpoint-native prompting and generation defaults."""
from __future__ import annotations
from dataclasses import dataclass, field, asdict
import math
import re
EOD = 151643
ABC_START, ABC_END = 151847, 151848
MUSIC_START, MUSIC_END = 151851, 151852
CODEC_OFFSET, CODEC_SIZE = 151853, 32768
LATENT_START, LATENT_END, LATENT_PAD = 184621, 184622, 184623
VOCAB_SIZE, CONTEXT = 184704, 24576
PROTOCOL_VERSION = "yue2-native-v1"
INSTRUCTIONS = {
"off": "Generate music with codec tokens from the given conditions.",
"melody": "Generate a melody-only ABC transcription without chord symbols, then generate music with codec tokens from the given conditions.",
"full": "Generate a chord-annotated ABC transcription, then generate music with codec tokens from the given conditions.",
}
@dataclass(frozen=True)
class Sampling:
temperature: float = 1.0
top_p: float = 0.95
top_k: int = 100
repetition_penalty: float = 1.2
penalty_window: int = 50
min_tokens: int = 200
max_tokens: int = 9000
def __post_init__(self):
if any(type(x) is not int for x in (self.top_k, self.penalty_window, self.min_tokens, self.max_tokens)):
raise ValueError("Sampling counts must be integers")
if not all(math.isfinite(x) for x in (self.temperature, self.top_p, self.repetition_penalty)):
raise ValueError("Sampling numbers must be finite")
if not 0 <= self.temperature <= 5 or not 0 < self.top_p <= 1 or self.top_k < 1:
raise ValueError("Invalid sampling temperature/top_p/top_k")
if self.repetition_penalty <= 0 or not 1 <= self.penalty_window <= 100:
raise ValueError("Invalid repetition penalty/window")
if not 0 <= self.min_tokens <= self.max_tokens or self.max_tokens < 1:
raise ValueError("Require 0 <= min_tokens <= max_tokens")
@dataclass(frozen=True)
class GenerationConfig:
abc: Sampling = field(default_factory=lambda: Sampling(.7, .9, 30, 1.005, 100, 32, 4096))
semantic: Sampling = field(default_factory=Sampling)
ode_steps: int = 32
ode_method: str = "midpoint"
context: int = CONTEXT
version: str = PROTOCOL_VERSION
def __post_init__(self):
if self.context != CONTEXT or self.ode_method != "midpoint" or type(self.ode_steps) is not int or self.ode_steps < 1:
raise ValueError("Require context=24576 and midpoint with positive integer steps")
def to_dict(self):
return asdict(self)
@classmethod
def from_dict(cls, data):
value = dict(data)
defaults = cls()
for key in ("abc", "semantic"):
if key in value and isinstance(value[key], dict):
value[key] = Sampling(**{**asdict(getattr(defaults, key)), **value[key]})
return cls(**value)
def resolve_sampling(value, default):
if value is None:
return default
if isinstance(value, Sampling):
return value
if isinstance(value, dict):
return Sampling(**{**asdict(default), **value})
raise TypeError("Sampling must be a Sampling object or a dictionary of overrides")
@dataclass(frozen=True)
class SongRequest:
style: str
lyrics: str
cot: str = "full"
seed: int = 831001
abc: str | None = None
cfg_scale: float | None = None
id: str = "song"
def __post_init__(self):
if self.cot not in INSTRUCTIONS:
raise ValueError("cot must be off, melody or full")
if not isinstance(self.style, str) or not isinstance(self.lyrics, str):
raise TypeError("style and lyrics must be strings")
if type(self.seed) is not int or not 0 <= self.seed < 2**63:
raise ValueError("seed must be an integer in [0, 2**63)")
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,179}", self.id) or self.id in {".", ".."}:
raise ValueError("id must be a filename-safe identifier")
if self.abc is not None and (self.cot == "off" or not isinstance(self.abc, str) or not self.abc.strip()):
raise ValueError("External ABC requires nonempty text and cot=melody/full")
if self.cfg_scale is not None and (not math.isfinite(self.cfg_scale) or not 0 <= self.cfg_scale <= 20):
raise ValueError("cfg_scale must be finite and in [0,20]")
@property
def guidance(self):
return (1.01 if self.cot == "off" else 1.0) if self.cfg_scale is None else self.cfg_scale
def text(self):
return f"{INSTRUCTIONS[self.cot]}\n[Tags]\n{self.style}\n[Lyrics]\n{self.lyrics}\n"
def to_dict(self):
return asdict(self)
def token_prefixes(request, tokenizer, abc_ids=None):
base = [EOD] + tokenizer.encode(request.text())
if request.cot == "off":
return base + [ABC_START, ABC_END, MUSIC_START]
if abc_ids is None:
if request.abc is None:
return base + [ABC_START]
abc_ids = tokenizer.encode(request.abc)
abc_ids = list(abc_ids)
if any(type(token) is not int or not 0 <= token < EOD for token in abc_ids):
raise ValueError("ABC IDs must remain inside the ordinary text vocabulary")
return base + [ABC_START] + abc_ids + [ABC_END, MUSIC_START]
def negative_prefix(request, tokenizer, abc_ids=None):
base = [EOD] + tokenizer.encode(INSTRUCTIONS[request.cot])
if request.cot == "off":
return base + [MUSIC_START]
if abc_ids is None:
raise ValueError("Symbolic CFG must retain the exact positive-branch ABC IDs")
abc_ids = list(abc_ids)
if any(type(token) is not int or not 0 <= token < EOD for token in abc_ids):
raise ValueError("ABC IDs must remain inside the ordinary text vocabulary")
return base + [ABC_START] + abc_ids + [ABC_END, MUSIC_START]
def chunk_ranges(frames, prefix_tokens, context=CONTEXT):
size = min((context - prefix_tokens - 3) // 2, CONTEXT)
if frames < 1 or size < 1:
raise ValueError("Empty codec or prefix leaves no acoustic context")
return [(a, min(a + size, frames)) for a in range(0, frames, size)]