Skip to main content

rlx_moshi/
config.rs

1use serde::{Deserialize, Serialize};
2
3/// Positional embedding style for a transformer stack.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5pub enum PositionalEmbedding {
6    Rope,
7    Sin,
8    None,
9}
10
11/// Single transformer stack config (temporal or depth).
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct TransformerConfig {
14    pub d_model: usize,
15    pub num_heads: usize,
16    pub num_layers: usize,
17    pub dim_feedforward: usize,
18    pub causal: bool,
19    pub norm_first: bool,
20    pub context: usize,
21    pub max_period: usize,
22    pub positional_embedding: PositionalEmbedding,
23    pub kv_repeat: usize,
24}
25
26impl TransformerConfig {
27    /// SwiGLU hidden width when `dim_feedforward == 4 * d_model` (Kyutai Moshi v0.1).
28    pub fn swiglu_hidden(&self) -> usize {
29        if self.dim_feedforward == 4 * self.d_model {
30            11 * self.d_model / 4
31        } else {
32            2 * self.dim_feedforward / 3
33        }
34    }
35}
36
37/// Depth decoder config — one mini-transformer per generated codebook.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct DepFormerConfig {
40    pub transformer: TransformerConfig,
41    pub num_slices: usize,
42}
43
44/// Full LM config (Helium temporal + optional DepFormer).
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct LmConfig {
47    pub transformer: TransformerConfig,
48    pub depformer: Option<DepFormerConfig>,
49    pub text_in_vocab_size: usize,
50    pub text_out_vocab_size: usize,
51    pub audio_vocab_size: usize,
52    pub audio_codebooks: usize,
53}
54
55impl LmConfig {
56    fn depformer_cfg(num_slices: usize) -> DepFormerConfig {
57        DepFormerConfig {
58            num_slices,
59            transformer: TransformerConfig {
60                d_model: 1024,
61                num_heads: 16,
62                num_layers: 6,
63                dim_feedforward: 1024 * 4,
64                causal: true,
65                norm_first: true,
66                context: num_slices,
67                max_period: 10_000,
68                positional_embedding: PositionalEmbedding::None,
69                kv_repeat: 1,
70            },
71        }
72    }
73
74    /// Moshiko / Moshika 7B (8 generated + 8 user codebooks).
75    pub fn v0_1() -> Self {
76        Self {
77            transformer: TransformerConfig {
78                d_model: 4096,
79                num_heads: 32,
80                num_layers: 32,
81                dim_feedforward: 4096 * 4,
82                causal: true,
83                norm_first: true,
84                context: 3000,
85                max_period: 10_000,
86                positional_embedding: PositionalEmbedding::Rope,
87                kv_repeat: 1,
88            },
89            depformer: Some(Self::depformer_cfg(8)),
90            audio_vocab_size: 2049,
91            text_in_vocab_size: 32_001,
92            text_out_vocab_size: 32_000,
93            audio_codebooks: 8,
94        }
95    }
96
97    /// Full-duplex with 16 audio embedding tables (8 Moshi + 8 user).
98    pub fn v0_1_streaming(num_slices: usize) -> Self {
99        let mut s = Self::v0_1();
100        s.audio_codebooks = 16;
101        if let Some(dep) = s.depformer.as_mut() {
102            dep.num_slices = num_slices;
103            dep.transformer.context = num_slices;
104        }
105        s
106    }
107}
108
109/// Runtime generation hyper-parameters (acoustic delay, codebook counts).
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct GenerateConfig {
112    pub generated_audio_codebooks: usize,
113    pub input_audio_codebooks: usize,
114    pub audio_vocab_size: usize,
115    pub acoustic_delay: usize,
116    pub text_pad_token: u32,
117    pub text_eop_token: u32,
118    pub text_start_token: u32,
119}
120
121impl GenerateConfig {
122    pub fn v0_1() -> Self {
123        Self {
124            generated_audio_codebooks: 8,
125            input_audio_codebooks: 8,
126            audio_vocab_size: 2049,
127            acoustic_delay: 2,
128            text_eop_token: 0,
129            text_pad_token: 3,
130            text_start_token: 32_000,
131        }
132    }
133
134    /// TTS / one-way: no user audio codebooks.
135    pub fn v0_1_one_way() -> Self {
136        Self {
137            generated_audio_codebooks: 8,
138            input_audio_codebooks: 0,
139            ..Self::v0_1()
140        }
141    }
142
143    pub fn audio_pad_token(&self) -> u32 {
144        self.audio_vocab_size as u32 - 1
145    }
146
147    pub fn total_audio_codebooks(&self) -> usize {
148        self.generated_audio_codebooks + self.input_audio_codebooks
149    }
150}
151
152/// Kyutai Moshi voice family — routes HuggingFace repos for a given quant preset.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
154pub enum MoshiVoice {
155    /// Male voice (`kyutai/moshiko-*` repos).
156    Moshiko,
157    /// Female voice (`kyutai/moshika-*` repos).
158    Moshika,
159}
160
161impl MoshiVoice {
162    /// Parse CLI / env voice names (`moshiko`, `moshika`, `male`, `female`).
163    pub fn parse(name: &str) -> Option<Self> {
164        match name.to_ascii_lowercase().as_str() {
165            "moshiko" | "male" => Some(Self::Moshiko),
166            "moshika" | "female" => Some(Self::Moshika),
167            _ => None,
168        }
169    }
170
171    /// Read `RLX_MOSHI_VOICE`, defaulting to Moshiko.
172    pub fn from_env_or_default() -> Self {
173        std::env::var("RLX_MOSHI_VOICE")
174            .ok()
175            .and_then(|s| Self::parse(&s))
176            .unwrap_or(Self::Moshiko)
177    }
178}
179
180/// Runtime mode: voice family × one-way vs full-duplex codebook layout.
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum MoshiVariant {
183    /// Moshiko full-duplex (8+8 codebooks).
184    Moshiko,
185    /// Moshiko one-way TTS-style (8 generated codebooks only).
186    MoshikoOneWay,
187    /// Moshika full-duplex.
188    Moshika,
189    /// Moshika one-way TTS-style.
190    MoshikaOneWay,
191}
192
193impl MoshiVariant {
194    /// Voice family for HuggingFace checkpoint routing.
195    pub fn voice(self) -> MoshiVoice {
196        match self {
197            Self::Moshiko | Self::MoshikoOneWay => MoshiVoice::Moshiko,
198            Self::Moshika | Self::MoshikaOneWay => MoshiVoice::Moshika,
199        }
200    }
201
202    /// True for TTS-style presets with no user audio codebooks.
203    pub fn is_one_way(self) -> bool {
204        matches!(self, Self::MoshikoOneWay | Self::MoshikaOneWay)
205    }
206
207    /// True for full-duplex presets (user + Moshi audio streams).
208    pub fn is_duplex(self) -> bool {
209        matches!(self, Self::Moshiko | Self::Moshika)
210    }
211
212    /// Parse CLI variant names (`moshiko`, `moshika-one-way`, `duplex`, …).
213    pub fn parse(name: &str) -> Option<Self> {
214        match name.to_ascii_lowercase().as_str() {
215            "moshiko-one-way" | "moshiko-oneway" | "oneway" => Some(Self::MoshikoOneWay),
216            "moshiko" | "duplex" => Some(Self::Moshiko),
217            "moshika-one-way" | "moshika-oneway" => Some(Self::MoshikaOneWay),
218            "moshika" => Some(Self::Moshika),
219            _ => None,
220        }
221    }
222
223    /// HuggingFace repo for this variant and checkpoint preset.
224    pub fn hf_repo(self, checkpoint: super::checkpoint::MoshiCheckpoint) -> &'static str {
225        checkpoint.hf_repo(self.voice())
226    }
227
228    /// Static LM architecture config (layer counts, codebook tables).
229    pub fn lm_config(self) -> LmConfig {
230        match self {
231            Self::Moshiko | Self::Moshika => LmConfig::v0_1_streaming(8),
232            Self::MoshikoOneWay | Self::MoshikaOneWay => LmConfig::v0_1(),
233        }
234    }
235
236    /// Per-generation hyper-parameters (acoustic delay, pad tokens).
237    pub fn generate_config(self) -> GenerateConfig {
238        match self {
239            Self::Moshiko | Self::Moshika => GenerateConfig::v0_1(),
240            Self::MoshikoOneWay | Self::MoshikaOneWay => GenerateConfig::v0_1_one_way(),
241        }
242    }
243}