use std::path::{Path, PathBuf};
use ndarray::Array2;
use tracing::debug;
use crate::inference::cuda::{
CudaError, CudaRuntime, CudaSegmentation, CudaSession, SafetensorsFile, SegmentationOptions,
};
use crate::inference::{ExecutionMode, InferenceError, ModelLoadError};
use crate::pipeline::RuntimeConfig;
use super::SegmentationError;
use crate::inference::cuda::implementation::policy::RecipeMode;
pub(super) struct CudaSegmentationBackend {
session: CudaSession<SegmentationState>,
#[cfg_attr(feature = "coreml", allow(dead_code))]
weights: PathBuf,
#[cfg_attr(feature = "coreml", allow(dead_code))]
options: SegmentationOptions,
#[cfg_attr(feature = "coreml", allow(dead_code))]
recipe_mode: RecipeMode,
window_samples: usize,
staging: Vec<f32>,
}
struct SegmentationState(CudaSegmentation);
unsafe impl Send for CudaSession<SegmentationState> {}
impl CudaSegmentationBackend {
pub(super) fn load(
model_path: &Path,
mode: ExecutionMode,
window_samples: usize,
config: &RuntimeConfig,
) -> Result<Self, ModelLoadError> {
let weights = model_path.with_extension("safetensors");
if !weights.is_file() {
return Err(ModelLoadError::MissingCudaWeights {
mode,
path: weights,
});
}
let options = SegmentationOptions {
math: config.cuda_segmentation_math,
#[cfg(feature = "_cuda-libraries")]
lstm_algo: config.cuda_lstm_algorithm,
cuda_graph: config.cuda_graphs.enabled(),
};
let recipe_mode =
RecipeMode::new(config.cuda_segmentation_math, config.cuda_embedding_math);
let (session, options) = open_session(&weights, options, window_samples, recipe_mode)?;
Ok(Self {
session,
weights,
options,
recipe_mode,
window_samples,
staging: Vec::new(),
})
}
#[cfg(not(feature = "coreml"))]
pub(super) fn reload(&self) -> Result<Self, InferenceError> {
let (session, options) = open_session(
&self.weights,
self.options,
self.window_samples,
self.recipe_mode,
)?;
Ok(Self {
session,
weights: self.weights.clone(),
options,
recipe_mode: self.recipe_mode,
window_samples: self.window_samples,
staging: Vec::new(),
})
}
pub(super) fn run_window(&mut self, window: &[f32]) -> Result<Array2<f32>, SegmentationError> {
let mut outputs = self.run_batch(&[window])?;
outputs.pop().ok_or(SegmentationError::MalformedOutput {
context: "cuda segmentation window output",
message: "no output for the window".to_owned(),
})
}
pub(super) fn run_batch(
&mut self,
windows: &[&[f32]],
) -> Result<Vec<Array2<f32>>, SegmentationError> {
let samples = self.window_samples;
self.staging.clear();
self.staging.resize(windows.len() * samples, 0.0);
for (row, window) in self.staging.chunks_exact_mut(samples).zip(windows) {
let len = window.len().min(samples);
row[..len].copy_from_slice(&window[..len]);
}
let batch = windows.len();
let staging = &self.staging;
let output = self
.session
.run(|runtime, state| state.0.run(runtime, batch, staging))
.map_err(InferenceError::from)?;
logit_rows(output, batch)
}
pub(super) fn run_span(
&mut self,
span: &[f32],
starts: &[usize],
) -> Result<Vec<Array2<f32>>, SegmentationError> {
let samples = self.window_samples;
let output = self
.session
.run(|runtime, state| state.0.run_span(runtime, samples, span, starts))
.map_err(InferenceError::from)?;
logit_rows(output, starts.len())
}
}
fn logit_rows(output: Vec<f32>, batch: usize) -> Result<Vec<Array2<f32>>, SegmentationError> {
let stride = output.len().checked_div(batch).unwrap_or(0);
let frames = stride / CudaSegmentation::CLASSES;
output
.chunks_exact(stride.max(1))
.map(|row| {
Array2::from_shape_vec((frames, CudaSegmentation::CLASSES), row.to_vec()).map_err(
|error| SegmentationError::MalformedOutput {
context: "cuda segmentation batch output",
message: format!("invalid output shape: {error}"),
},
)
})
.collect()
}
fn open_session(
weights: &Path,
options: SegmentationOptions,
window_samples: usize,
recipe_mode: RecipeMode,
) -> Result<(CudaSession<SegmentationState>, SegmentationOptions), CudaError> {
let file = SafetensorsFile::open(weights)?;
let session = CudaSession::new(
CudaRuntime::new(0)?.with_recipe_mode(recipe_mode),
|runtime| {
let mut model = CudaSegmentation::new(runtime, &file, options)?;
for batch in [1, 32] {
model.workspace(runtime, batch, window_samples)?;
}
debug!(capability = %runtime.compute_capability(), ptx_tier = %model.kernel_tier(), ?options, "Loaded CUDA segmentation");
Ok(SegmentationState(model))
},
)?;
Ok((session, options))
}