@@ -92,6 +92,48 @@ void load_style(const std::string& path, std::vector<float>& ttl, std::vector<fl
9292 dp = extract_style (text, " style_dp" );
9393}
9494
95+ // Physical input slot per signature role, parsed from the tensor names of the model's main
96+ // subgraph (`serving_default_args_N[:0]`, N = signature position). ai_edge_torch / litert_torch
97+ // permute the slots away from the signature order, and differently per toolchain version: the
98+ // published base graphs bind [text_mask, text_ids, style_dp] where a current re-export binds
99+ // [text_ids, style_dp, text_mask]. When a name cannot be parsed, or the slots do not form a
100+ // permutation, `legacy` (the published bundle's introspected order) is used.
101+ template <size_t N>
102+ std::array<int , N> input_slots (LiteRtModel model, const std::array<int , N>& legacy, const char * what) {
103+ std::array<int , N> slots;
104+ slots.fill (-1 );
105+ LiteRtParamIndex main = 0 ;
106+ LiteRtSubgraph sg = nullptr ;
107+ LiteRtParamIndex n = 0 ;
108+ if (LiteRtGetMainModelSubgraphIndex (model, &main) == kLiteRtStatusOk &&
109+ LiteRtGetModelSubgraph (model, main, &sg) == kLiteRtStatusOk &&
110+ LiteRtGetNumSubgraphInputs (sg, &n) == kLiteRtStatusOk && n == N) {
111+ for (LiteRtParamIndex i = 0 ; i < n; ++i) {
112+ LiteRtTensor t = nullptr ;
113+ const char * nm = nullptr ;
114+ if (LiteRtGetSubgraphInput (sg, i, &t) != kLiteRtStatusOk || !t ||
115+ LiteRtGetTensorName (t, &nm) != kLiteRtStatusOk || !nm) continue ;
116+ const std::string name (nm);
117+ const size_t pos = name.find (" args_" );
118+ if (pos == std::string::npos) continue ;
119+ const int role = std::atoi (name.c_str () + pos + 5 );
120+ if (role >= 0 && role < static_cast <int >(N) && slots[static_cast <size_t >(role)] < 0 )
121+ slots[static_cast <size_t >(role)] = static_cast <int >(i);
122+ }
123+ }
124+ for (int s : slots) {
125+ if (s < 0 ) {
126+ LOGI (" Supertonic: %s input names not parseable; using the published slot order" , what);
127+ return legacy;
128+ }
129+ }
130+ return slots;
131+ }
132+
133+ constexpr std::array<int , 3 > kLegacyDurationSlots = {1 , 2 , 0 }; // [text_mask, text_ids, style_dp]
134+ constexpr std::array<int , 3 > kLegacyEncoderSlots = {1 , 2 , 0 }; // [text_mask, text_ids, style_ttl]
135+ constexpr std::array<int , 7 > kLegacyVectorSlots = {3 , 6 , 1 , 2 , 5 , 0 , 4 }; // [cur, ttl, lmask, noisy, tot, tmask, emb]
136+
95137// Near-silence gate for the seam trims below (-46 dBFS; the vocoder's silence floor is well under it).
96138constexpr float kSeamSilenceGate = 0 .005f ;
97139
@@ -135,6 +177,8 @@ LiteRTSupertonicTts::LiteRTSupertonicTts(const std::string& duration_path,
135177 auto & engine = LiteRTEngine::get ();
136178 engine.load (duration_path, hw_accel, &duration_model_, &duration_compiled_);
137179 engine.load (text_encoder_path, hw_accel, &encoder_model_, &encoder_compiled_);
180+ duration_slots_ = input_slots<3 >(duration_model_, kLegacyDurationSlots , " duration_predictor" );
181+ encoder_slots_ = input_slots<3 >(encoder_model_, kLegacyEncoderSlots , " text_encoder" );
138182
139183 // Base bucket: the graphs given explicitly, loaded now. Larger buckets are optional
140184 // `*_L{N}.tflite` siblings, discovered here and loaded on first use (see bucket_for()).
@@ -188,6 +232,7 @@ void LiteRTSupertonicTts::load_bucket(LatentBucket& b) {
188232 auto & engine = LiteRTEngine::get ();
189233 engine.load (b.vector_path , hw_accel_, &b.vector_model , &b.vector_compiled );
190234 engine.load (b.vocoder_path , hw_accel_, &b.vocoder_model , &b.vocoder_compiled );
235+ b.vector_slots = input_slots<7 >(b.vector_model , kLegacyVectorSlots , " vector_estimator" );
191236}
192237
193238// Register `vector_estimator_L<N>.tflite` + `vocoder_L<N>.tflite` pairs found next to the base
@@ -391,13 +436,15 @@ LiteRTSupertonicTts::Prepared LiteRTSupertonicTts::prepare_chunk(const std::stri
391436 LiteRtHostBuffer in_dp (env, t_dp, voice.style_dp .size () * sizeof (float ), voice.style_dp .data ());
392437
393438 // --- 1) duration_predictor → duration[1] ---
394- // LiteRtRunCompiledModel binds ins[] by the graph's tensor-INDEX order, which the ai_edge_torch
395- // export permutes away from the (args_0=ids, args_1=style_dp, args_2=text_mask) declaration.
396- // Introspected order of the published duration_predictor.tflite: [text_mask, text_ids, style_dp] .
439+ // LiteRtRunCompiledModel binds ins[] by the graph's input-slot order, which the converter
440+ // permutes away from the (args_0=ids, args_1=style_dp, args_2=text_mask) signature; the slot
441+ // per role was resolved from the tensor names at load (input_slots()) .
397442 float duration = 0 .0f ;
398443 {
399444 LiteRtHostBuffer out (env, t_dur, sizeof (float ));
400- LiteRtTensorBuffer ins[3 ] = { in_mask.raw (), in_ids.raw (), in_dp.raw () };
445+ const LiteRtTensorBuffer by_role[3 ] = { in_ids.raw (), in_dp.raw (), in_mask.raw () };
446+ LiteRtTensorBuffer ins[3 ] = {};
447+ for (size_t r = 0 ; r < 3 ; ++r) ins[duration_slots_[r]] = by_role[r];
401448 LiteRtTensorBuffer outs[1 ] = { out.raw () };
402449 litert_check (LiteRtRunCompiledModel (duration_compiled_, 0 , 3 , ins, 1 , outs),
403450 " duration_predictor Run" );
@@ -431,12 +478,13 @@ std::vector<float> LiteRTSupertonicTts::synth_prepared(const Prepared& p, size_t
431478 LiteRtHostBuffer in_mask (env, t_mask, p.tok .mask .size () * sizeof (float ), p.tok .mask .data ());
432479 LiteRtHostBuffer in_ttl (env, t_ttl, voice.style_ttl .size () * sizeof (float ), voice.style_ttl .data ());
433480
434- // --- 2) text_encoder → text_emb[1,256,T] ---
435- // Introspected tensor-index order of text_encoder.tflite: [text_mask, text_ids, style_ttl].
481+ // --- 2) text_encoder → text_emb[1,256,T] --- (roles: text_ids, style_ttl, text_mask)
436482 std::vector<float > text_emb (static_cast <size_t >(256 ) * kTextT );
437483 {
438484 LiteRtHostBuffer out (env, t_emb, text_emb.size () * sizeof (float ));
439- LiteRtTensorBuffer ins[3 ] = { in_mask.raw (), in_ids.raw (), in_ttl.raw () };
485+ const LiteRtTensorBuffer by_role[3 ] = { in_ids.raw (), in_ttl.raw (), in_mask.raw () };
486+ LiteRtTensorBuffer ins[3 ] = {};
487+ for (size_t r = 0 ; r < 3 ; ++r) ins[encoder_slots_[r]] = by_role[r];
440488 LiteRtTensorBuffer outs[1 ] = { out.raw () };
441489 litert_check (LiteRtRunCompiledModel (encoder_compiled_, 0 , 3 , ins, 1 , outs),
442490 " text_encoder Run" );
@@ -492,10 +540,12 @@ std::vector<float> LiteRTSupertonicTts::synth_prepared(const Prepared& p, size_t
492540 LiteRtHostBuffer in_tot (env, t_step, sizeof (float ), &total_step_f);
493541 LiteRtHostBuffer out (env, t_lat, xt.size () * sizeof (float ));
494542
495- // Introspected tensor-index order of vector_estimator.tflite:
496- // [current_step, style_ttl, latent_mask, noisy, total_step, text_mask, text_emb].
497- LiteRtTensorBuffer ins[7 ] = { in_cur.raw (), in_ttl.raw (), in_lmask.raw (),
498- in_noisy.raw (), in_tot.raw (), in_mask.raw (), in_emb.raw () };
543+ // Roles: noisy, text_emb, style_ttl, latent_mask, text_mask, current_step, total_step —
544+ // placed into the bucket's physical slots (resolved from tensor names at load).
545+ const LiteRtTensorBuffer by_role[7 ] = { in_noisy.raw (), in_emb.raw (), in_ttl.raw (), in_lmask.raw (),
546+ in_mask.raw (), in_cur.raw (), in_tot.raw () };
547+ LiteRtTensorBuffer ins[7 ] = {};
548+ for (size_t r = 0 ; r < 7 ; ++r) ins[bucket.vector_slots [r]] = by_role[r];
499549 LiteRtTensorBuffer outs[1 ] = { out.raw () };
500550 litert_check (LiteRtRunCompiledModel (bucket.vector_compiled , 0 , 7 , ins, 1 , outs),
501551 " vector_estimator Run" );
0 commit comments