| |
| |
| |
| |
| |
|
|
| #include "zipvoice_engine.hpp" |
|
|
| #include <fstream> |
| #include <sstream> |
| #include <cmath> |
| #include <cstring> |
| #include <algorithm> |
| #include <sys/time.h> |
|
|
| #ifndef M_PI |
| #define M_PI 3.14159265358979323846 |
| #endif |
|
|
| |
| |
| namespace { |
|
|
| std::string json_get_string(const std::string& json, const std::string& key) { |
| std::string search = "\"" + key + "\""; |
| size_t pos = json.find(search); |
| if (pos == std::string::npos) return ""; |
|
|
| pos = json.find(':', pos + search.length()); |
| if (pos == std::string::npos) return ""; |
|
|
| pos = json.find('"', pos + 1); |
| if (pos == std::string::npos) return ""; |
|
|
| size_t end = json.find('"', pos + 1); |
| if (end == std::string::npos) return ""; |
|
|
| return json.substr(pos + 1, end - pos - 1); |
| } |
|
|
| int json_get_int(const std::string& json, const std::string& key, int default_val = 0) { |
| std::string search = "\"" + key + "\""; |
| size_t pos = json.find(search); |
| if (pos == std::string::npos) return default_val; |
|
|
| pos = json.find(':', pos + search.length()); |
| if (pos == std::string::npos) return default_val; |
|
|
| |
| pos++; |
| while (pos < json.length() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n')) pos++; |
| if (pos >= json.length()) return default_val; |
|
|
| |
| std::string num_str; |
| while (pos < json.length() && (std::isdigit(json[pos]) || json[pos] == '-' || json[pos] == '.')) { |
| num_str += json[pos]; |
| pos++; |
| } |
| if (num_str.empty()) return default_val; |
|
|
| |
| if (num_str.find('.') != std::string::npos) { |
| return static_cast<int>(std::stof(num_str)); |
| } |
| return std::stoi(num_str); |
| } |
|
|
| float json_get_float(const std::string& json, const std::string& key, float default_val = 0.0f) { |
| std::string search = "\"" + key + "\""; |
| size_t pos = json.find(search); |
| if (pos == std::string::npos) return default_val; |
|
|
| pos = json.find(':', pos + search.length()); |
| if (pos == std::string::npos) return default_val; |
|
|
| pos++; |
| while (pos < json.length() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\n')) pos++; |
| if (pos >= json.length()) return default_val; |
|
|
| std::string num_str; |
| while (pos < json.length() && (std::isdigit(json[pos]) || json[pos] == '-' || json[pos] == '.')) { |
| num_str += json[pos]; |
| pos++; |
| } |
| if (num_str.empty()) return default_val; |
| return std::stof(num_str); |
| } |
|
|
| std::vector<std::string> json_get_string_array(const std::string& json, const std::string& key) { |
| std::vector<std::string> result; |
| std::string search = "\"" + key + "\""; |
| size_t pos = json.find(search); |
| if (pos == std::string::npos) return result; |
|
|
| pos = json.find('[', pos + search.length()); |
| if (pos == std::string::npos) return result; |
|
|
| size_t end = json.find(']', pos); |
| if (end == std::string::npos) return result; |
|
|
| std::string array_str = json.substr(pos + 1, end - pos - 1); |
|
|
| size_t start = 0; |
| while (start < array_str.length()) { |
| size_t q1 = array_str.find('"', start); |
| if (q1 == std::string::npos) break; |
| size_t q2 = array_str.find('"', q1 + 1); |
| if (q2 == std::string::npos) break; |
| result.push_back(array_str.substr(q1 + 1, q2 - q1 - 1)); |
| start = q2 + 1; |
| } |
| return result; |
| } |
|
|
| std::string read_file_content(const std::string& path) { |
| std::ifstream file(path); |
| if (!file.is_open()) return ""; |
| std::stringstream ss; |
| ss << file.rdbuf(); |
| return ss.str(); |
| } |
|
|
| } |
|
|
| ZipVoiceEngine::ZipVoiceEngine() |
| : m_has_init(false), m_decoder_seq_len(0), m_decoder_has_padding_mask(false) {} |
|
|
| ZipVoiceEngine::~ZipVoiceEngine() { |
| m_sessions.clear(); |
| } |
|
|
| double ZipVoiceEngine::GetCurrentTimeMs() { |
| struct timeval tv; |
| gettimeofday(&tv, nullptr); |
| return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0; |
| } |
|
|
| int ZipVoiceEngine::Init(const std::string& model_dir, const char* axclConfig) { |
| if (LoadConfig(model_dir) != 0) return -1; |
| if (LoadManifest(model_dir) != 0) return -1; |
| if (LoadModels(model_dir, axclConfig) != 0) return -1; |
| if (LoadDecoderMetadata() != 0) return -1; |
|
|
| m_has_init = true; |
| printf("ZipVoiceEngine initialized: max_tokens=%d, max_feat_len=%d, feat_dim=%d, num_step=%d\n", |
| m_config.max_tokens, m_config.max_feat_len, m_config.feat_dim, m_config.num_step); |
| return 0; |
| } |
|
|
| int ZipVoiceEngine::LoadConfig(const std::string& model_dir) { |
| std::string config_path = model_dir + "/runtime_config.json"; |
| std::string json = read_file_content(config_path); |
| if (!json.empty()) { |
| m_config.max_tokens = json_get_int(json, "max_tokens", 384); |
| m_config.max_feat_len = json_get_int(json, "max_feat_len", 1024); |
| m_config.feat_dim = json_get_int(json, "feat_dim", 100); |
| m_config.sampling_rate = json_get_int(json, "sampling_rate", 24000); |
| m_config.hop_length = json_get_int(json, "hop_length", 256); |
| m_config.num_step = json_get_int(json, "num_step", 10); |
| m_config.t_shift = json_get_float(json, "t_shift", 0.5f); |
| m_config.guidance_scale = json_get_float(json, "guidance_scale", 1.0f); |
| } |
| m_config.model_dir = model_dir; |
| return 0; |
| } |
|
|
| int ZipVoiceEngine::LoadManifest(const std::string& model_dir) { |
| std::string manifest_path = model_dir + "/decoder4_split_manifest.json"; |
| std::string json = read_file_content(manifest_path); |
| if (json.empty()) { |
| printf("Failed to read manifest: %s\n", manifest_path.c_str()); |
| return -1; |
| } |
|
|
| |
| m_encoder_info.name = json_get_string(json, "name"); |
| |
| size_t enc_pos = json.find("\"encoder\""); |
| if (enc_pos != std::string::npos) { |
| size_t obj_start = json.find('{', enc_pos); |
| size_t obj_end = json.find('}', obj_start); |
| if (obj_start != std::string::npos && obj_end != std::string::npos) { |
| std::string enc_json = json.substr(obj_start, obj_end - obj_start + 1); |
| m_encoder_info.name = json_get_string(enc_json, "name"); |
| m_encoder_info.file = json_get_string(enc_json, "file"); |
| m_encoder_info.inputs = json_get_string_array(enc_json, "inputs"); |
| m_encoder_info.outputs = json_get_string_array(enc_json, "outputs"); |
| } |
| } |
|
|
| |
| size_t dec_pos = json.find("\"decoder_parts\""); |
| if (dec_pos != std::string::npos) { |
| size_t arr_start = json.find('[', dec_pos); |
| if (arr_start != std::string::npos) { |
| m_decoder_parts.clear(); |
| size_t pos = arr_start + 1; |
| int depth = 0; |
| std::string part_json; |
| for (; pos < json.length(); ++pos) { |
| if (json[pos] == '{') depth++; |
| if (depth > 0) part_json += json[pos]; |
| if (json[pos] == '}') { |
| depth--; |
| if (depth == 0) { |
| |
| ModelInfo info; |
| info.name = json_get_string(part_json, "name"); |
| info.file = json_get_string(part_json, "file"); |
| info.inputs = json_get_string_array(part_json, "inputs"); |
| info.outputs = json_get_string_array(part_json, "outputs"); |
| m_decoder_parts.push_back(info); |
| part_json.clear(); |
| } |
| } |
| } |
| } |
| } |
|
|
| printf("Manifest: encoder=%s, decoder_parts=%zu\n", |
| m_encoder_info.name.c_str(), m_decoder_parts.size()); |
| for (size_t i = 0; i < m_decoder_parts.size(); ++i) { |
| printf(" part%zu: %s (%s) in=%zu out=%zu\n", i, |
| m_decoder_parts[i].name.c_str(), m_decoder_parts[i].file.c_str(), |
| m_decoder_parts[i].inputs.size(), m_decoder_parts[i].outputs.size()); |
| } |
| return 0; |
| } |
|
|
| int ZipVoiceEngine::LoadModels(const std::string& model_dir, const char* axclConfig) { |
| |
| std::string enc_path = model_dir + "/" + m_encoder_info.file; |
| auto enc = std::make_unique<EngineWrapper>(); |
| if (enc->Init(enc_path.c_str(), 0, axclConfig) != 0) { |
| printf("Failed to load encoder: %s\n", enc_path.c_str()); |
| return -1; |
| } |
| m_sessions[m_encoder_info.name] = std::move(enc); |
|
|
| |
| for (auto& part : m_decoder_parts) { |
| std::string path = model_dir + "/" + part.file; |
| auto sess = std::make_unique<EngineWrapper>(); |
| if (sess->Init(path.c_str(), 0, axclConfig) != 0) { |
| printf("Failed to load decoder part: %s\n", path.c_str()); |
| return -1; |
| } |
| m_sessions[part.name] = std::move(sess); |
| } |
|
|
| printf("Loaded %zu models\n", m_sessions.size()); |
| return 0; |
| } |
|
|
| int ZipVoiceEngine::LoadDecoderMetadata() { |
| if (m_decoder_parts.empty()) return -1; |
|
|
| auto& part0 = m_decoder_parts[0]; |
| auto it = m_sessions.find(part0.name); |
| if (it == m_sessions.end()) return -1; |
|
|
| auto& sess = it->second; |
|
|
| |
| int pad_idx = sess->GetInputIndex("padding_mask"); |
| m_decoder_has_padding_mask = (pad_idx >= 0); |
|
|
| |
| m_decoder_seq_len = m_config.max_feat_len; |
|
|
| |
| |
|
|
| printf("Decoder metadata: seq_len=%d, has_padding_mask=%d\n", |
| m_decoder_seq_len, m_decoder_has_padding_mask ? 1 : 0); |
| return 0; |
| } |
|
|
| std::vector<float> ZipVoiceEngine::GetTimesteps(int num_step, float t_shift) const { |
| std::vector<float> ts(num_step + 1); |
| for (int i = 0; i <= num_step; ++i) { |
| float t = static_cast<float>(i) / num_step; |
| ts[i] = t_shift * t / (1.0f + (t_shift - 1.0f) * t); |
| } |
| return ts; |
| } |
|
|
| int ZipVoiceEngine::RunEncoder(const std::vector<int32_t>& cat_tokens, |
| std::vector<float>& out_encoded) { |
| auto it = m_sessions.find(m_encoder_info.name); |
| if (it == m_sessions.end()) return -1; |
|
|
| auto& sess = it->second; |
|
|
| |
| int input_idx = sess->GetInputIndex(m_encoder_info.inputs[0].c_str()); |
| if (input_idx < 0) input_idx = 0; |
|
|
| |
| std::vector<int32_t> input_copy = cat_tokens; |
| sess->SetInput(input_copy.data(), input_idx); |
|
|
| |
| if (sess->RunSync() != 0) return -1; |
|
|
| |
| int output_idx = sess->GetOutputIndex(m_encoder_info.outputs[0].c_str()); |
| if (output_idx < 0) output_idx = 0; |
|
|
| int output_size = sess->GetOutputSize(output_idx); |
| out_encoded.resize(output_size / sizeof(float)); |
| sess->GetOutput(out_encoded.data(), output_idx); |
|
|
| return 0; |
| } |
|
|
| int ZipVoiceEngine::DurationExpand(const std::vector<float>& encoded, |
| int prompt_tokens_len, |
| int text_tokens_len, |
| int prompt_features_len, |
| float speed, |
| std::vector<float>& out_text_condition, |
| int& out_features_len) { |
| int total_tokens_len = prompt_tokens_len + text_tokens_len; |
| if (total_tokens_len <= 0) return -1; |
|
|
| |
| int features_len = static_cast<int>( |
| std::ceil(static_cast<float>(prompt_features_len) / prompt_tokens_len |
| * total_tokens_len / speed) |
| ); |
| if (features_len > m_config.max_feat_len) { |
| features_len = m_config.max_feat_len; |
| } |
|
|
| int feat_dim = m_config.feat_dim; |
|
|
| |
| |
| int token_dur = features_len / total_tokens_len; |
| int max_tokens = m_config.max_tokens; |
|
|
| |
| out_text_condition.assign(features_len * feat_dim, 0.0f); |
|
|
| |
| for (int t = 0; t < total_tokens_len; ++t) { |
| int base_idx = t * feat_dim; |
| for (int d = 0; d < token_dur; ++d) { |
| int frame_idx = t * token_dur + d; |
| if (frame_idx >= features_len) break; |
| int dst_idx = frame_idx * feat_dim; |
| std::copy(encoded.begin() + base_idx, |
| encoded.begin() + base_idx + feat_dim, |
| out_text_condition.begin() + dst_idx); |
| } |
| } |
|
|
| |
| int filled = total_tokens_len * token_dur; |
| int residual = features_len - filled; |
| if (residual > 0) { |
| int last_base = total_tokens_len * feat_dim; |
| for (int d = 0; d < residual; ++d) { |
| int dst_idx = (filled + d) * feat_dim; |
| std::copy(encoded.begin() + last_base, |
| encoded.begin() + last_base + feat_dim, |
| out_text_condition.begin() + dst_idx); |
| } |
| } |
|
|
| out_features_len = features_len; |
| return 0; |
| } |
|
|
| int ZipVoiceEngine::RunDecoderPart(const ModelInfo& part, |
| std::map<std::string, std::vector<float>>& values, |
| const std::vector<uint8_t>* padding_mask_data, |
| std::vector<uint8_t>* padding_mask2_out) { |
| auto it = m_sessions.find(part.name); |
| if (it == m_sessions.end()) return -1; |
| auto& sess = it->second; |
|
|
| |
| for (size_t i = 0; i < part.inputs.size(); ++i) { |
| const std::string& expected_name = part.inputs[i]; |
|
|
| int input_idx = sess->GetInputIndex(expected_name.c_str()); |
| if (input_idx < 0) input_idx = static_cast<int>(i); |
|
|
| |
| if (expected_name == "padding_mask") { |
| if (padding_mask_data) { |
| sess->SetInput((void*)padding_mask_data->data(), input_idx); |
| continue; |
| } |
| } |
| |
| if (expected_name == "padding_mask2") { |
| if (padding_mask2_out && !padding_mask2_out->empty()) { |
| sess->SetInput((void*)padding_mask2_out->data(), input_idx); |
| continue; |
| } |
| printf("ERROR: padding_mask2 not available for '%s'\n", part.name.c_str()); |
| return -1; |
| } |
|
|
| |
| auto val_it = values.find(expected_name); |
| if (val_it == values.end()) { |
| printf("Missing input '%s' for model '%s'\n", |
| expected_name.c_str(), part.name.c_str()); |
| return -1; |
| } |
|
|
| sess->SetInput((void*)val_it->second.data(), input_idx); |
| } |
|
|
| |
| if (sess->RunSync() != 0) return -1; |
|
|
| |
| for (size_t i = 0; i < part.outputs.size(); ++i) { |
| const std::string& name = part.outputs[i]; |
|
|
| int output_idx = sess->GetOutputIndex(name.c_str()); |
| if (output_idx < 0) output_idx = static_cast<int>(i); |
|
|
| int size = sess->GetOutputSize(output_idx); |
| if (size <= 0) { |
| printf("Invalid output size for '%s' in '%s'\n", |
| name.c_str(), part.name.c_str()); |
| return -1; |
| } |
|
|
| |
| if (name == "padding_mask2" && padding_mask2_out) { |
| padding_mask2_out->resize(size); |
| sess->GetOutput(padding_mask2_out->data(), output_idx); |
| continue; |
| } |
|
|
| std::vector<float> output_data(size / sizeof(float)); |
| sess->GetOutput(output_data.data(), output_idx); |
| values[name] = std::move(output_data); |
| } |
|
|
| return 0; |
| } |
|
|
| int ZipVoiceEngine::Sample(const std::vector<int32_t>& cat_tokens, |
| int prompt_tokens_len, |
| int text_tokens_len, |
| const std::vector<float>& prompt_features, |
| int prompt_features_len, |
| float speed, |
| float guidance_scale, |
| int seed, |
| std::vector<float>& out_features, |
| Timing& out_timing) { |
| double t_total_start = GetCurrentTimeMs(); |
|
|
| |
| double t_start = GetCurrentTimeMs(); |
| std::vector<float> encoded; |
| if (RunEncoder(cat_tokens, encoded) != 0) return -1; |
| out_timing.encoder_time_sec = static_cast<float>(GetCurrentTimeMs() - t_start) / 1000.0f; |
|
|
| |
| int feat_dim = m_config.feat_dim; |
|
|
| |
| t_start = GetCurrentTimeMs(); |
| std::vector<float> text_condition; |
| int features_len; |
| if (DurationExpand(encoded, prompt_tokens_len, text_tokens_len, |
| prompt_features_len, speed, |
| text_condition, features_len) != 0) return -1; |
| out_timing.duration_expand_time_sec = static_cast<float>(GetCurrentTimeMs() - t_start) / 1000.0f; |
|
|
| |
| int seq_len = m_decoder_seq_len > 0 ? m_decoder_seq_len : m_config.max_feat_len; |
|
|
| if (features_len > seq_len) { |
| printf("features_len=%d exceeds decoder seq_len=%d\n", features_len, seq_len); |
| return -1; |
| } |
| if (prompt_features_len > seq_len) { |
| printf("prompt_features_len=%d exceeds decoder seq_len=%d\n", prompt_features_len, seq_len); |
| return -1; |
| } |
|
|
| |
| std::vector<float> text_cond_padded(seq_len * feat_dim, 0.0f); |
| std::copy(text_condition.begin(), |
| text_condition.begin() + features_len * feat_dim, |
| text_cond_padded.begin()); |
|
|
| |
| std::vector<float> speech_cond_padded(seq_len * feat_dim, 0.0f); |
| std::copy(prompt_features.begin(), |
| prompt_features.begin() + prompt_features_len * feat_dim, |
| speech_cond_padded.begin()); |
| |
|
|
| |
| |
| std::vector<uint8_t> padding_mask(seq_len, 0); |
| for (int i = features_len; i < seq_len; ++i) { |
| padding_mask[i] = 1; |
| } |
|
|
| |
| std::vector<float> x(seq_len * feat_dim, 0.0f); |
| |
| uint32_t rng_state = static_cast<uint32_t>(seed > 0 ? seed : 42); |
| for (int i = 0; i < features_len * feat_dim; ++i) { |
| rng_state = rng_state * 1103515245 + 12345; |
| |
| float u1 = static_cast<float>(rng_state & 0x7FFFFFFF) / 0x7FFFFFFF; |
| rng_state = rng_state * 1103515245 + 12345; |
| float u2 = static_cast<float>(rng_state & 0x7FFFFFFF) / 0x7FFFFFFF; |
| x[i] = std::sqrt(-2.0f * std::log(std::max(u1, 1e-10f))) |
| * std::cos(2.0f * M_PI * u2); |
| } |
|
|
| |
| std::vector<float> timesteps = GetTimesteps(m_config.num_step, m_config.t_shift); |
|
|
| |
| std::vector<float> gs = { guidance_scale }; |
|
|
| |
| double t_dec_total = 0.0; |
| for (int step = 0; step < m_config.num_step; ++step) { |
| double t_step_start = GetCurrentTimeMs(); |
|
|
| |
| std::map<std::string, std::vector<float>> values; |
|
|
| |
| std::vector<float> t_val = { timesteps[step] }; |
| values["t"] = t_val; |
|
|
| |
| values["x"] = x; |
|
|
| |
| values["text_condition"] = text_cond_padded; |
|
|
| |
| values["speech_condition"] = speech_cond_padded; |
|
|
| |
| values["guidance_scale"] = gs; |
|
|
| |
| std::vector<uint8_t> padding_mask2_data; |
|
|
| |
| for (auto& part : m_decoder_parts) { |
| if (RunDecoderPart(part, values, &padding_mask, &padding_mask2_data) != 0) return -1; |
| } |
|
|
| t_dec_total += GetCurrentTimeMs() - t_step_start; |
|
|
| |
| const std::string& final_output_name = m_decoder_parts.back().outputs[0]; |
| auto vit = values.find(final_output_name); |
| if (vit == values.end()) { |
| printf("Missing final output '%s'\n", final_output_name.c_str()); |
| return -1; |
| } |
|
|
| const std::vector<float>& v = vit->second; |
|
|
| |
| float dt = timesteps[step + 1] - timesteps[step]; |
| for (size_t i = 0; i < x.size(); ++i) { |
| x[i] += v[i] * dt; |
| } |
|
|
| |
| for (int i = features_len * feat_dim; i < seq_len * feat_dim; ++i) { |
| x[i] = 0.0f; |
| } |
| } |
|
|
| out_timing.decoder_time_sec = static_cast<float>(t_dec_total) / 1000.0f; |
|
|
| |
| int generated_frames = features_len - prompt_features_len; |
| if (generated_frames <= 0) { |
| generated_frames = features_len; |
| out_features.assign(x.begin(), x.begin() + features_len * feat_dim); |
| } else { |
| int offset = prompt_features_len * feat_dim; |
| out_features.assign(x.begin() + offset, |
| x.begin() + features_len * feat_dim); |
| } |
|
|
| out_timing.generated_frames = generated_frames; |
| out_timing.features_len = features_len; |
| out_timing.total_time_sec = static_cast<float>(GetCurrentTimeMs() - t_total_start) / 1000.0f; |
|
|
| printf(" encoder: %.3f s dur_expand: %.3f s decoder(%d steps): %.3f s (avg %.3f ms/step) total: %.3f s\n", |
| out_timing.encoder_time_sec, |
| out_timing.duration_expand_time_sec, |
| m_config.num_step, |
| out_timing.decoder_time_sec, |
| out_timing.decoder_time_sec / m_config.num_step * 1000.0f, |
| out_timing.total_time_sec); |
|
|
| return 0; |
| } |
|
|