mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-10 03:51:25 +00:00
inference job handlers
This commit is contained in:
1 parent
8755e7a533
commit
b128cdb3f5
5 files changed
+379
-31
No files matched your search
@@ -28,10 +28,8 @@ pub async fn process_single_job(job_dependencies: &JobDependencies, job: &Availa
|
||||
|
||||
// ==================== SETUP TEMP DIRS ==================== //
|
||||
|
||||
// TODO: Scoped temp dir
|
||||
|
||||
let temp_dir = format!("temp_{}", job.id.0);
|
||||
let temp_dir = TempDir::new(&temp_dir)?;
|
||||
let temp_dir = job_dependencies.scoped_temp_dir_creator.new_tempdir(&temp_dir)?;
|
||||
|
||||
// ==================== HANDLE DIFFERENT INFERENCE TYPES ==================== //
|
||||
|
||||
|
||||
@@ -31,20 +31,6 @@ use tts_common::clean_symbols::clean_symbols;
|
||||
use tts_common::text_pipelines::guess_pipeline::guess_text_pipeline_heuristic;
|
||||
use tts_common::text_pipelines::text_pipeline_type::TextPipelineType;
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
struct FileMetadata {
|
||||
pub duration_millis: Option<u64>,
|
||||
pub mimetype: Option<String>,
|
||||
pub file_size_bytes: u64,
|
||||
}
|
||||
|
||||
fn read_metadata_file(filename: &PathBuf) -> AnyhowResult<FileMetadata> {
|
||||
let mut file = File::open(filename)?;
|
||||
let mut buffer = String::new();
|
||||
file.read_to_string(&mut buffer)?;
|
||||
Ok(serde_json::from_str(&buffer)?)
|
||||
}
|
||||
|
||||
pub async fn process_single_job_old(
|
||||
job_args: &JobDependencies,
|
||||
job: &AvailableTtsInferenceJob,
|
||||
@@ -53,24 +39,24 @@ pub async fn process_single_job_old(
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let span_id = generate_random_uuid();
|
||||
let trace_id = generate_random_uuid();
|
||||
let maybe_user_token = job.maybe_creator_user_token.as_deref().unwrap_or("");
|
||||
//let span_id = generate_random_uuid();
|
||||
//let trace_id = generate_random_uuid();
|
||||
//let maybe_user_token = job.maybe_creator_user_token.as_deref().unwrap_or("");
|
||||
|
||||
let mut job_iteration_span = Span::new(&span_id, &trace_id, get_timestamp_millis())
|
||||
.name("single job execution")
|
||||
.attribute("user_token", maybe_user_token)
|
||||
.service_name("tts-inference-job");
|
||||
//let mut job_iteration_span = Span::new(&span_id, &trace_id, get_timestamp_millis())
|
||||
// .name("single job execution")
|
||||
// .attribute("user_token", maybe_user_token)
|
||||
// .service_name("tts-inference-job");
|
||||
|
||||
let span_id = generate_random_uuid();
|
||||
let trace_id = generate_random_uuid();
|
||||
//let span_id = generate_random_uuid();
|
||||
//let trace_id = generate_random_uuid();
|
||||
|
||||
let created_at_timestamp = (job.created_at.timestamp() as u64) * 1000;
|
||||
//let created_at_timestamp = (job.created_at.timestamp() as u64) * 1000;
|
||||
|
||||
let mut since_creation_span = Span::new(&span_id, &trace_id, created_at_timestamp)
|
||||
.name("job since creation")
|
||||
.attribute("user_token", maybe_user_token)
|
||||
.service_name("tts-inference-job");
|
||||
//let mut since_creation_span = Span::new(&span_id, &trace_id, created_at_timestamp)
|
||||
// .name("job since creation")
|
||||
// .attribute("user_token", maybe_user_token)
|
||||
// .service_name("tts-inference-job");
|
||||
|
||||
let mut job_progress_reporter = job_args
|
||||
.job_progress_reporter
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
use anyhow::anyhow;
|
||||
use log::{info, warn};
|
||||
use tempdir::TempDir;
|
||||
use crate::job_steps::job_dependencies::JobDependencies;
|
||||
use crate::job_steps::process_single_job_error::ProcessSingleJobError;
|
||||
use database_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
|
||||
use errors::AnyhowResult;
|
||||
use tts_common::clean_symbols::clean_symbols;
|
||||
use crate::util::download_file_from_bucket::maybe_download_file_from_bucket;
|
||||
|
||||
pub async fn handle_tts_inference(
|
||||
job_dependencies: &JobDependencies,
|
||||
job: &AvailableInferenceJob,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
let mut job_progress_reporter = job_dependencies
|
||||
.job_progress_reporter
|
||||
.new_tts_inference(job.inference_job_token.as_str())
|
||||
.map_err(|e| ProcessSingleJobError::Other(anyhow!(e)))?;
|
||||
|
||||
// ==================== CONFIRM OR DOWNLOAD WAVEGLOW VOCODER MODEL ==================== //
|
||||
|
||||
let waveglow_vocoder_model_fs_path = {
|
||||
let waveglow_vocoder_model_filename = job_dependencies.waveglow_vocoder_model_filename.clone();
|
||||
let waveglow_vocoder_model_fs_path = job_dependencies.semi_persistent_cache.tts_pretrained_vocoder_model_path(&waveglow_vocoder_model_filename);
|
||||
let waveglow_vocoder_model_object_path = job_dependencies.bucket_path_unifier.tts_pretrained_vocoders_path(&waveglow_vocoder_model_filename);
|
||||
|
||||
maybe_download_file_from_bucket(
|
||||
"waveglow vocoder model",
|
||||
&waveglow_vocoder_model_fs_path,
|
||||
&waveglow_vocoder_model_object_path,
|
||||
&job_dependencies.private_bucket_client,
|
||||
&mut job_progress_reporter,
|
||||
"downloading vocoder (1 of 3)",
|
||||
job.id.0,
|
||||
&job_dependencies.scoped_temp_dir_creator,
|
||||
).await?;
|
||||
|
||||
waveglow_vocoder_model_fs_path
|
||||
};
|
||||
|
||||
// ==================== CONFIRM OR DOWNLOAD HIFIGAN (NORMAL) VOCODER MODEL ==================== //
|
||||
|
||||
let pretrained_hifigan_vocoder_model_fs_path = {
|
||||
let hifigan_vocoder_model_filename = job_dependencies.hifigan_vocoder_model_filename.clone();
|
||||
let hifigan_vocoder_model_fs_path = job_dependencies.semi_persistent_cache.tts_pretrained_vocoder_model_path(&hifigan_vocoder_model_filename);
|
||||
let hifigan_vocoder_model_object_path = job_dependencies.bucket_path_unifier.tts_pretrained_vocoders_path(&hifigan_vocoder_model_filename);
|
||||
|
||||
maybe_download_file_from_bucket(
|
||||
"hifigan vocoder model",
|
||||
&hifigan_vocoder_model_fs_path,
|
||||
&hifigan_vocoder_model_object_path,
|
||||
&job_dependencies.private_bucket_client,
|
||||
&mut job_progress_reporter,
|
||||
"downloading vocoder (2 of 3)",
|
||||
job.id.0,
|
||||
&job_dependencies.scoped_temp_dir_creator,
|
||||
).await?;
|
||||
|
||||
hifigan_vocoder_model_fs_path
|
||||
};
|
||||
|
||||
// ==================== CONFIRM OR DOWNLOAD HIFIGAN (SUPERRES) VOCODER MODEL ==================== //
|
||||
|
||||
let hifigan_superres_vocoder_model_fs_path = {
|
||||
let hifigan_superres_vocoder_model_filename = job_dependencies.hifigan_superres_vocoder_model_filename.clone();
|
||||
let hifigan_superres_vocoder_model_fs_path = job_dependencies.semi_persistent_cache.tts_pretrained_vocoder_model_path(&hifigan_superres_vocoder_model_filename);
|
||||
let hifigan_superres_vocoder_model_object_path = job_dependencies.bucket_path_unifier.tts_pretrained_vocoders_path(&hifigan_superres_vocoder_model_filename);
|
||||
|
||||
maybe_download_file_from_bucket(
|
||||
"hifigan superres vocoder model",
|
||||
&hifigan_superres_vocoder_model_fs_path,
|
||||
&hifigan_superres_vocoder_model_object_path,
|
||||
&job_dependencies.private_bucket_client,
|
||||
&mut job_progress_reporter,
|
||||
"downloading vocoder (3 of 3)",
|
||||
job.id.0,
|
||||
&job_dependencies.scoped_temp_dir_creator,
|
||||
).await?;
|
||||
|
||||
hifigan_superres_vocoder_model_fs_path
|
||||
};
|
||||
|
||||
// // ==================== CONFIRM OR DOWNLOAD OPTIONAL CUSTOM VOCODER MODEL ==================== //
|
||||
|
||||
let custom_vocoder_fs_path = match &model_record.maybe_custom_vocoder {
|
||||
None => None,
|
||||
Some(vocoder) => {
|
||||
let custom_vocoder_fs_path = job_dependencies.semi_persistent_cache.custom_vocoder_model_path(&vocoder.vocoder_token);
|
||||
let custom_vocoder_object_path = job_dependencies.bucket_path_unifier.vocoder_path(&vocoder.vocoder_private_bucket_hash);
|
||||
|
||||
maybe_download_file_from_bucket(
|
||||
"custom vocoder",
|
||||
&custom_vocoder_fs_path,
|
||||
&custom_vocoder_object_path,
|
||||
&job_dependencies.private_bucket_client,
|
||||
&mut job_progress_reporter,
|
||||
"downloading user vocoder",
|
||||
job.id.0,
|
||||
&job_dependencies.scoped_temp_dir_creator,
|
||||
).await?;
|
||||
|
||||
Some(custom_vocoder_fs_path)
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== CONFIRM OR DOWNLOAD TTS SYNTHESIZER MODEL ==================== //
|
||||
|
||||
let tts_synthesizer_fs_path = {
|
||||
let tts_synthesizer_fs_path = job_dependencies.semi_persistent_cache.tts_synthesizer_model_path(&model_record.model_token);
|
||||
let tts_synthesizer_object_path = job_dependencies.bucket_path_unifier.tts_synthesizer_path(&model_record.private_bucket_hash);
|
||||
|
||||
maybe_download_file_from_bucket(
|
||||
"synthesizer",
|
||||
&tts_synthesizer_fs_path,
|
||||
&tts_synthesizer_object_path,
|
||||
&job_dependencies.private_bucket_client,
|
||||
&mut job_progress_reporter,
|
||||
"downloading synthesizer",
|
||||
job.id.0,
|
||||
&job_dependencies.scoped_temp_dir_creator,
|
||||
).await?;
|
||||
|
||||
tts_synthesizer_fs_path
|
||||
};
|
||||
|
||||
// ==================== Preprocess text ==================== //
|
||||
|
||||
let cleaned_inference_text = clean_symbols(&job.raw_inference_text);
|
||||
|
||||
// ==================== WRITE TEXT TO FILE ==================== //
|
||||
|
||||
info!("Creating tempdir for inference results.");
|
||||
|
||||
let temp_dir = format!("temp_tts_inference_{}", job.id.0);
|
||||
|
||||
// NB: TempDir exists until it goes out of scope, at which point it should delete from filesystem.
|
||||
let temp_dir = TempDir::new(&temp_dir)
|
||||
.map_err(|e| ProcessSingleJobError::from_io_error(e))?;
|
||||
|
||||
let text_input_fs_path = temp_dir.path().join("inference_input.txt");
|
||||
|
||||
std::fs::write(&text_input_fs_path, &cleaned_inference_text)
|
||||
.map_err(|e| ProcessSingleJobError::from_io_error(e))?;
|
||||
|
||||
// ==================== RUN INFERENCE ==================== //
|
||||
|
||||
job_progress_reporter.log_status("running inference")
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
// TODO: Fix this.
|
||||
let maybe_unload_model_path = job_dependencies
|
||||
.virtual_model_lfu
|
||||
.insert_returning_replaced(tts_synthesizer_fs_path.to_str().unwrap_or(""))
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
if let Some(model_path) = maybe_unload_model_path.as_deref() {
|
||||
warn!("Remove model from LFU cache: {:?}", model_path);
|
||||
}
|
||||
|
||||
let output_audio_fs_path = temp_dir.path().join("output.wav");
|
||||
let output_metadata_fs_path = temp_dir.path().join("metadata.json");
|
||||
let output_spectrogram_fs_path = temp_dir.path().join("spectrogram.json");
|
||||
|
||||
info!("Running TTS inference...");
|
||||
|
||||
info!("Expected output audio filename: {:?}", &output_audio_fs_path);
|
||||
info!("Expected output spectrogram filename: {:?}", &output_spectrogram_fs_path);
|
||||
info!("Expected output metadata filename: {:?}", &output_metadata_fs_path);
|
||||
|
||||
if let Some(model_path) = maybe_unload_model_path.as_deref() {
|
||||
warn!("Unload model from sidecar: {:?}", &model_path);
|
||||
}
|
||||
|
||||
let mut pretrained_vocoder = VocoderType::HifiGanSuperResolution;
|
||||
if let Some(default_vocoder) = model_record.maybe_default_pretrained_vocoder.as_deref() {
|
||||
pretrained_vocoder = VocoderType::from_str(default_vocoder)
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
}
|
||||
|
||||
info!("With pretrained vocoder: {:?}", pretrained_vocoder);
|
||||
|
||||
let text_pipeline_type_or_guess = model_record.text_pipeline_type
|
||||
.as_deref()
|
||||
.and_then(|pipeline_type|
|
||||
TextPipelineType::from_str(pipeline_type).ok())// NB: If there's an error deserializing, turn it to None.
|
||||
.unwrap_or_else(||
|
||||
guess_text_pipeline_heuristic(Some(model_record.created_at)));
|
||||
|
||||
info!("With text pipeline type `{:?} ` (or guess: {:?})",
|
||||
&model_record.text_pipeline_type,
|
||||
&text_pipeline_type_or_guess);
|
||||
|
||||
let hifigan_vocoder_model_fs_path_to_use = match custom_vocoder_fs_path {
|
||||
None => {
|
||||
info!("using pretrained HiFi-GAN vocoder");
|
||||
pretrained_hifigan_vocoder_model_fs_path
|
||||
},
|
||||
Some(custom_vocoder_fs_path) => {
|
||||
info!("using custom user-trained HiFi-GAN vocoder: {:?}", custom_vocoder_fs_path);
|
||||
custom_vocoder_fs_path
|
||||
},
|
||||
};
|
||||
|
||||
// NB: Tacotron operates on decoder steps. 1000 steps is the default and correlates to
|
||||
// roughly 12 seconds max. Here we map seconds to decoder steps.
|
||||
let max_decoder_steps = seconds_to_decoder_steps(job.max_duration_seconds);
|
||||
|
||||
job_dependencies.http_clients.tts_inference_sidecar_client.request_inference(
|
||||
&cleaned_inference_text,
|
||||
max_decoder_steps,
|
||||
&tts_synthesizer_fs_path,
|
||||
pretrained_vocoder,
|
||||
&text_pipeline_type_or_guess.to_str(),
|
||||
&hifigan_vocoder_model_fs_path_to_use,
|
||||
&hifigan_superres_vocoder_model_fs_path,
|
||||
&waveglow_vocoder_model_fs_path,
|
||||
&output_audio_fs_path,
|
||||
&output_spectrogram_fs_path,
|
||||
&output_metadata_fs_path,
|
||||
maybe_unload_model_path)
|
||||
.await
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
// ==================== CHECK ALL FILES EXIST AND GET METADATA ==================== //
|
||||
|
||||
info!("Checking that output files exist...");
|
||||
|
||||
check_file_exists(&output_audio_fs_path).map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
check_file_exists(&output_spectrogram_fs_path).map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
check_file_exists(&output_metadata_fs_path).map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
let file_metadata = read_metadata_file(&output_metadata_fs_path)
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
safe_delete_temp_file(&output_metadata_fs_path);
|
||||
|
||||
// ==================== UPLOAD AUDIO TO BUCKET ==================== //
|
||||
|
||||
job_progress_reporter.log_status("uploading result")
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
let audio_result_object_path = job_dependencies.bucket_path_unifier.tts_inference_wav_audio_output_path(
|
||||
&job.uuid_idempotency_token); // TODO: Don't use this!
|
||||
|
||||
info!("Audio destination bucket path: {:?}", &audio_result_object_path);
|
||||
|
||||
info!("Uploading audio...");
|
||||
|
||||
job_dependencies.public_bucket_client.upload_filename_with_content_type(
|
||||
&audio_result_object_path,
|
||||
&output_audio_fs_path,
|
||||
"audio/wav")
|
||||
.await
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
safe_delete_temp_file(&output_audio_fs_path);
|
||||
|
||||
// ==================== UPLOAD SPECTROGRAM TO BUCKETS ==================== //
|
||||
|
||||
let spectrogram_result_object_path = job_dependencies.bucket_path_unifier.tts_inference_spectrogram_output_path(
|
||||
&job.uuid_idempotency_token); // TODO: Don't use this!
|
||||
|
||||
info!("Spectrogram destination bucket path: {:?}", &spectrogram_result_object_path);
|
||||
|
||||
info!("Uploading spectrogram...");
|
||||
|
||||
job_dependencies.public_bucket_client.upload_filename_with_content_type(
|
||||
&spectrogram_result_object_path,
|
||||
&output_spectrogram_fs_path,
|
||||
"application/json")
|
||||
.await
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
safe_delete_temp_file(&output_spectrogram_fs_path);
|
||||
|
||||
// ==================== DELETE DOWNLOADED FILE ==================== //
|
||||
|
||||
// NB: We should be using a tempdir, but to make absolutely certain we don't overflow the disk...
|
||||
safe_delete_temp_directory(&temp_dir);
|
||||
|
||||
// ==================== SAVE RECORDS ==================== //
|
||||
|
||||
let text_hash = hash_string_sha2(&cleaned_inference_text)
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
let worker_name = job_dependencies.get_worker_name();
|
||||
|
||||
info!("Saving tts inference record...");
|
||||
|
||||
let (id, inference_result_token) = insert_tts_result(
|
||||
&job_dependencies.mysql_pool,
|
||||
job,
|
||||
&text_hash,
|
||||
pretrained_vocoder,
|
||||
&audio_result_object_path,
|
||||
&spectrogram_result_object_path,
|
||||
file_metadata.file_size_bytes,
|
||||
file_metadata.duration_millis.unwrap_or(0),
|
||||
job_dependencies.worker_details.is_on_prem,
|
||||
&worker_name,
|
||||
job_dependencies.worker_details.is_debug_worker)
|
||||
.await
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
info!("Marking job complete...");
|
||||
mark_tts_inference_job_done(
|
||||
&job_dependencies.mysql_pool,
|
||||
job.id,
|
||||
true,
|
||||
Some(&inference_result_token),
|
||||
&worker_name)
|
||||
.await
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
info!("TTS Done. Original text was: {}", &job.raw_inference_text);
|
||||
|
||||
job_dependencies.firehose_publisher.tts_inference_finished(
|
||||
job.maybe_creator_user_token.as_deref(),
|
||||
&model_record.model_token,
|
||||
&inference_result_token)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("error publishing event: {:?}", e);
|
||||
ProcessSingleJobError::Other(anyhow!("error publishing event"))
|
||||
})?;
|
||||
|
||||
job_progress_reporter.log_status("done")
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
info!("Job {:?} complete success! Downloaded, ran inference, and uploaded. Saved model record: {}, Result Token: {}",
|
||||
job.id, id, &inference_result_token);
|
||||
|
||||
let duration = start.elapsed();
|
||||
|
||||
since_creation_span.set_attribute("status", "success");
|
||||
since_creation_span.set_duration(duration);
|
||||
|
||||
job_iteration_span.set_attribute("status", "success");
|
||||
job_iteration_span.set_duration(duration);
|
||||
|
||||
Ok((since_creation_span, job_iteration_span))
|
||||
Ok(())
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
pub mod handle_tts_inference;
|
||||
pub mod read_metadata_file;
|
||||
pub mod seconds_to_decoder_steps;
|
||||
@@ -0,0 +1,18 @@
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct FileMetadata {
|
||||
pub duration_millis: Option<u64>,
|
||||
pub mimetype: Option<String>,
|
||||
pub file_size_bytes: u64,
|
||||
}
|
||||
|
||||
pub fn read_metadata_file(filename: &PathBuf) -> AnyhowResult<FileMetadata> {
|
||||
let mut file = File::open(filename)?;
|
||||
let mut buffer = String::new();
|
||||
file.read_to_string(&mut buffer)?;
|
||||
Ok(serde_json::from_str(&buffer)?)
|
||||
}
|
||||
Reference in new issue
Block a user