1use crate::checkpoint::MoshiCheckpoint;
4use crate::config::{GenerateConfig, MoshiVariant};
5use crate::generate::GenerateState;
6use crate::lm::LmModel;
7use crate::sampling::LogitsProcessor;
8use crate::weights::{open_lm, open_lm_from_weights};
9use anyhow::{Result, ensure};
10use rlx_runtime::Device;
11use std::path::Path;
12
13#[cfg(feature = "gpu-lm")]
14use crate::gpu::{GpuGenerateState, GpuLm, gpu_lm_available, rlx_device_to_candle};
15
16#[cfg(feature = "compiled-lm")]
17pub use crate::compiled_lm::{CompiledGenerateState, CompiledLm, compiled_lm_available};
18
19pub enum MoshiLm {
21 Eager(LmModel),
22 #[cfg(feature = "gpu-lm")]
23 Gpu(GpuLm),
24}
25
26pub enum MoshiGenState {
28 Eager(GenerateState),
29 #[cfg(feature = "gpu-lm")]
30 Gpu(GpuGenerateState),
31}
32
33impl MoshiLm {
34 pub fn open(
35 model_dir: &Path,
36 variant: MoshiVariant,
37 checkpoint: MoshiCheckpoint,
38 device: Device,
39 ) -> Result<Self> {
40 if checkpoint.is_mlx() && device == Device::Cpu {
41 let weights_path = checkpoint.lm_weights_path(model_dir);
42 let cfg = variant.lm_config();
43 let weights =
44 crate::mlx_weights::load_eager_weight_map(&weights_path, checkpoint, &cfg)?;
45 return Ok(Self::Eager(open_lm_from_weights(cfg, weights)?));
46 }
47 if checkpoint.is_mlx() {
48 #[cfg(not(feature = "gpu-lm"))]
49 anyhow::bail!(
50 "checkpoint {:?} requires `gpu-lm` or `--device cpu`",
51 checkpoint
52 );
53 }
54 let weights_path = checkpoint.lm_weights_path(model_dir);
55 let cfg = variant.lm_config();
56
57 #[cfg(feature = "compiled-lm")]
58 if device != Device::Cpu && compiled_lm_available(device) && checkpoint.gpu_loadable() {
59 return Ok(Self::Gpu(CompiledLm::open(
60 &weights_path,
61 variant,
62 checkpoint,
63 device,
64 )?));
65 }
66
67 #[cfg(all(feature = "gpu-lm", not(feature = "compiled-lm")))]
68 if device != Device::Cpu && gpu_lm_available(device) && checkpoint.gpu_loadable() {
69 return Ok(Self::Gpu(GpuLm::open(
70 &weights_path,
71 variant,
72 checkpoint,
73 device,
74 )?));
75 }
76
77 if device != Device::Cpu && checkpoint.gpu_loadable() {
78 #[cfg(feature = "gpu-lm")]
79 if gpu_lm_available(device) {
80 return Ok(Self::Gpu(GpuLm::open(
81 &weights_path,
82 variant,
83 checkpoint,
84 device,
85 )?));
86 }
87 #[cfg(not(feature = "gpu-lm"))]
88 anyhow::bail!(
89 "device {device:?} requested but crate built without `gpu-lm` — rebuild with --features gpu-lm,metal"
90 );
91 #[cfg(feature = "gpu-lm")]
92 anyhow::bail!("device {device:?} not available for Moshi GPU LM");
93 }
94
95 let lm = if checkpoint.is_gguf() {
96 let weights = crate::gguf::load_gguf_weight_map(&weights_path, &cfg)?;
97 open_lm_from_weights(cfg, weights)?
98 } else {
99 open_lm(model_dir, cfg)?
100 };
101 Ok(Self::Eager(lm))
102 }
103
104 pub fn config(&self) -> &crate::config::LmConfig {
105 match self {
106 Self::Eager(m) => m.config(),
107 #[cfg(feature = "gpu-lm")]
108 Self::Gpu(m) => m.config(),
109 }
110 }
111
112 pub fn reset_state(&mut self) {
113 match self {
114 Self::Eager(m) => m.reset_state(),
115 #[cfg(feature = "gpu-lm")]
116 Self::Gpu(_) => {}
117 }
118 }
119
120 pub fn new_gen_state(
121 &self,
122 max_steps: usize,
123 text_lp: LogitsProcessor,
124 audio_lp: LogitsProcessor,
125 gen_cfg: GenerateConfig,
126 ) -> Result<MoshiGenState> {
127 match self {
128 Self::Eager(_) => Ok(MoshiGenState::Eager(GenerateState::new(
129 max_steps, text_lp, audio_lp, gen_cfg,
130 ))),
131 #[cfg(feature = "gpu-lm")]
132 Self::Gpu(g) => Ok(MoshiGenState::Gpu(
133 g.new_generate_state(max_steps, text_lp, audio_lp, &gen_cfg)?,
134 )),
135 }
136 }
137}
138
139impl MoshiGenState {
140 pub fn config(&self) -> &GenerateConfig {
141 match self {
142 Self::Eager(s) => s.config(),
143 #[cfg(feature = "gpu-lm")]
144 Self::Gpu(s) => s.config(),
145 }
146 }
147
148 pub fn step_idx(&self) -> usize {
149 match self {
150 Self::Eager(s) => s.step_idx(),
151 #[cfg(feature = "gpu-lm")]
152 Self::Gpu(s) => s.step_idx(),
153 }
154 }
155
156 pub fn text_tokens(&self) -> &[u32] {
157 match self {
158 Self::Eager(s) => s.text_tokens(),
159 #[cfg(feature = "gpu-lm")]
160 Self::Gpu(s) => s.text_tokens(),
161 }
162 }
163
164 pub fn step(&mut self, lm: &mut MoshiLm, text_token: u32, input_audio: &[u32]) -> Result<u32> {
165 match (self, lm) {
166 (Self::Eager(s), MoshiLm::Eager(m)) => s.step(m, text_token, input_audio),
167 #[cfg(feature = "gpu-lm")]
168 (Self::Gpu(s), MoshiLm::Gpu(_m)) => s.step(text_token, input_audio),
169 #[allow(unreachable_patterns)]
170 _ => {
171 ensure!(false, "Moshi LM / generation backend mismatch");
172 Ok(0)
173 }
174 }
175 }
176
177 pub fn last_audio_frame(&self) -> Option<Vec<u32>> {
178 match self {
179 Self::Eager(s) => s.last_audio_frame(),
180 #[cfg(feature = "gpu-lm")]
181 Self::Gpu(s) => s.last_audio_frame(),
182 }
183 }
184
185 pub fn reset(
186 &mut self,
187 lm: &mut MoshiLm,
188 max_steps: usize,
189 text_lp: LogitsProcessor,
190 audio_lp: LogitsProcessor,
191 ) -> Result<()> {
192 match (self, lm) {
193 (Self::Eager(s), MoshiLm::Eager(m)) => {
194 let _ = (max_steps, text_lp, audio_lp);
195 s.reset(m);
196 Ok(())
197 }
198 #[cfg(feature = "gpu-lm")]
199 (Self::Gpu(s), MoshiLm::Gpu(g)) => s.reset(g, max_steps, text_lp, audio_lp),
200 #[allow(unreachable_patterns)]
201 _ => anyhow::bail!("Moshi LM / generation backend mismatch"),
202 }
203 }
204}
205
206pub fn resolve_lm_device(requested: Device, checkpoint: MoshiCheckpoint) -> Device {
208 if requested == Device::Cpu {
209 return Device::Cpu;
210 }
211 if !checkpoint.gpu_loadable() {
212 return Device::Cpu;
213 }
214 #[cfg(feature = "gpu-lm")]
215 {
216 if gpu_lm_available(requested) {
217 return requested;
218 }
219 }
220 Device::Cpu
221}
222
223#[cfg(feature = "gpu-lm")]
224pub fn candle_device_for(device: Device) -> Result<candle::Device> {
225 rlx_device_to_candle(device)
226}