binhqd commited on
Commit
c5017ba
·
1 Parent(s): 6775e28

Add custom inference handler for Maya1 TTS

Browse files
Files changed (1) hide show
  1. handler.py +53 -13
handler.py CHANGED
@@ -75,7 +75,7 @@ class EndpointHandler:
75
  "description": "... optional voice description ...",
76
  "generation_args": { optional dict for text generation params }
77
  }
78
-
79
  Returns dict with base64 audio:
80
  {
81
  "audio_base64": "<base64-encoded WAV data>",
@@ -84,23 +84,25 @@ class EndpointHandler:
84
  """
85
  # Extract inputs (HF always provides this key)
86
  inputs = data.get("inputs", "")
87
-
88
  # Get additional parameters from top level
89
  description = data.get("description", "")
90
  generation_args = data.get("generation_args", {})
91
-
92
  # Parse inputs
93
  if isinstance(inputs, dict):
94
  # inputs is a dict with text and description
95
  text = inputs.get("text", "")
96
- description = inputs.get("description", description) # override if in inputs
 
 
97
  elif isinstance(inputs, str):
98
  # inputs is just the text string
99
  text = inputs
100
  else:
101
  # Try to convert to string
102
  text = str(inputs) if inputs else ""
103
-
104
  if not text:
105
  return {"error": f"No text provided. Received: {data}"}
106
  prompt = description + "\n" + text
@@ -139,17 +141,55 @@ class EndpointHandler:
139
 
140
  # Tokenize and generate
141
  tokenizer_inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
142
- outputs = self.model.generate(**tokenizer_inputs, **generation_args)
 
 
 
 
 
 
 
 
 
 
143
  token_ids = outputs[0]
144
 
145
- # decode tokens to intermediate representation (for Maya1)
146
- # assuming model outputs token ids for audio generation — adjust as per model spec
147
- audio_feats = (
148
- token_ids # may need further decoding depending on how Maya1 works
149
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- # pass features to SNAC to synthesize waveform
152
- waveform = self.snac.decode(audio_feats).cpu().numpy() # shape (n_samples,)
153
 
154
  # convert waveform to bytes (e.g. WAV) using soundfile loaded into self.sf
155
  buf = io.BytesIO()
 
75
  "description": "... optional voice description ...",
76
  "generation_args": { optional dict for text generation params }
77
  }
78
+
79
  Returns dict with base64 audio:
80
  {
81
  "audio_base64": "<base64-encoded WAV data>",
 
84
  """
85
  # Extract inputs (HF always provides this key)
86
  inputs = data.get("inputs", "")
87
+
88
  # Get additional parameters from top level
89
  description = data.get("description", "")
90
  generation_args = data.get("generation_args", {})
91
+
92
  # Parse inputs
93
  if isinstance(inputs, dict):
94
  # inputs is a dict with text and description
95
  text = inputs.get("text", "")
96
+ description = inputs.get(
97
+ "description", description
98
+ ) # override if in inputs
99
  elif isinstance(inputs, str):
100
  # inputs is just the text string
101
  text = inputs
102
  else:
103
  # Try to convert to string
104
  text = str(inputs) if inputs else ""
105
+
106
  if not text:
107
  return {"error": f"No text provided. Received: {data}"}
108
  prompt = description + "\n" + text
 
141
 
142
  # Tokenize and generate
143
  tokenizer_inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
144
+
145
+ # Set default generation args if not provided
146
+ default_gen_args = {
147
+ "max_length": 2048,
148
+ "do_sample": True,
149
+ "temperature": 0.7,
150
+ "top_p": 0.9,
151
+ }
152
+ default_gen_args.update(generation_args)
153
+
154
+ outputs = self.model.generate(**tokenizer_inputs, **default_gen_args)
155
  token_ids = outputs[0]
156
 
157
+ # Maya1 generates SNAC audio codes in the output tokens
158
+ # Extract only the generated tokens (excluding input prompt tokens)
159
+ generated_ids = token_ids[tokenizer_inputs["input_ids"].shape[1] :]
160
+
161
+ # Convert token IDs to SNAC codes
162
+ # Maya1 outputs are structured as SNAC token IDs that need to be reshaped
163
+ # SNAC expects codes in shape (batch, num_codebooks, seq_len)
164
+ # Assuming Maya1 outputs tokens sequentially for each codebook
165
+ import torch
166
+
167
+ snac_codes = generated_ids.unsqueeze(0) # Add batch dimension
168
+
169
+ # SNAC 24kHz uses 7 codebooks - reshape accordingly
170
+ # The model outputs interleaved codes, so we need to split them
171
+ num_codebooks = 7
172
+ seq_len = len(generated_ids) // num_codebooks
173
+
174
+ if len(generated_ids) >= num_codebooks:
175
+ # Reshape to (1, num_codebooks, seq_len) for SNAC decoder
176
+ snac_codes = (
177
+ generated_ids[: seq_len * num_codebooks]
178
+ .reshape(num_codebooks, seq_len)
179
+ .unsqueeze(0)
180
+ )
181
+
182
+ # Decode using SNAC to synthesize waveform
183
+ with torch.no_grad():
184
+ waveform = self.snac.decode(snac_codes.to(self.device))
185
+
186
+ # Extract audio and convert to numpy
187
+ if waveform.dim() == 3: # (batch, channels, samples)
188
+ waveform = waveform.squeeze(0).squeeze(0) # Remove batch and channel dims
189
+ elif waveform.dim() == 2: # (batch, samples)
190
+ waveform = waveform.squeeze(0)
191
 
192
+ waveform = waveform.cpu().numpy()
 
193
 
194
  # convert waveform to bytes (e.g. WAV) using soundfile loaded into self.sf
195
  buf = io.BytesIO()