use super::*;
use crate::tensor::DataType;
macro_rules! codes {
($($(#[doc = $doc:literal])* $(#[cfg($cfg:meta)])? $name:ident = $code:literal,)*) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Code {
$($(#[doc = $doc])* $(#[cfg($cfg)])? $name,)*
}
impl Code {
#[cfg(test)]
pub(crate) const ALL: &[Code] = &[$($(#[cfg($cfg)])? Code::$name,)*];
pub(crate) fn as_str(self) -> &'static str {
match self {
$($(#[cfg($cfg)])? Code::$name => $code,)*
}
}
}
};
}
codes! {
InvalidRequest = "INVALID_REQUEST",
#[cfg(feature = "image")]
InvalidImage = "INVALID_IMAGE",
InvalidInput = "INVALID_INPUT",
InputValidationFailed = "INPUT_VALIDATION_FAILED",
InvalidInputValue = "INVALID_INPUT_VALUE",
BatchTooLarge = "BATCH_TOO_LARGE",
NamedTensorsRequired = "NAMED_TENSORS_REQUIRED",
ModelNotFound = "MODEL_NOT_FOUND",
Unauthorized = "UNAUTHORIZED",
RateLimited = "RATE_LIMITED",
ServerBusy = "SERVER_BUSY",
InferenceTimeout = "INFERENCE_TIMEOUT",
InferenceFailed = "INFERENCE_FAILED",
InternalError = "INTERNAL_ERROR",
AdminApiDisabled = "ADMIN_API_DISABLED",
NotReloadable = "NOT_RELOADABLE",
ReloadInProgress = "RELOAD_IN_PROGRESS",
ReloadFailed = "RELOAD_FAILED",
}
pub(crate) fn model_error_status(e: &FurnaceError) -> (StatusCode, Code) {
match e {
FurnaceError::Model(ModelError::InputValidation { .. }) => {
(StatusCode::BAD_REQUEST, Code::InputValidationFailed)
}
FurnaceError::Model(ModelError::EmptyBatch) => {
(StatusCode::BAD_REQUEST, Code::InvalidInput)
}
FurnaceError::Model(ModelError::BatchTooLarge { .. }) => {
(StatusCode::BAD_REQUEST, Code::BatchTooLarge)
}
e if e.is_client_error() => (StatusCode::BAD_REQUEST, Code::InvalidInputValue),
FurnaceError::Model(ModelError::InferenceFailed(_)) => {
(StatusCode::INTERNAL_SERVER_ERROR, Code::InferenceFailed)
}
_ => (StatusCode::INTERNAL_SERVER_ERROR, Code::InternalError),
}
}
#[derive(Debug)]
pub(crate) enum InferError {
Busy,
Timeout(Duration),
Lost,
Model(FurnaceError),
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Busy;
impl InferError {
pub(crate) fn status(&self) -> (StatusCode, Code) {
match self {
Self::Busy => (StatusCode::SERVICE_UNAVAILABLE, Code::ServerBusy),
Self::Timeout(_) => (StatusCode::GATEWAY_TIMEOUT, Code::InferenceTimeout),
Self::Lost => (StatusCode::INTERNAL_SERVER_ERROR, Code::InferenceFailed),
Self::Model(e) => model_error_status(e),
}
}
pub(crate) fn message(&self) -> String {
match self {
Self::Busy => "Too many concurrent requests, retry later".to_string(),
Self::Timeout(timeout) => {
format!("Inference exceeded timeout of {}ms", timeout.as_millis())
}
Self::Lost => "The inference ended without an answer (see the server log)".to_string(),
Self::Model(e) => e.to_string(),
}
}
pub(crate) fn into_response(self) -> Response {
match &self {
Self::Busy => warn!("All inference slots busy, rejecting request"),
Self::Timeout(timeout) => warn!("Inference exceeded timeout of {timeout:?}"),
Self::Lost => error!("Prediction error: {}", self.message()),
Self::Model(e) if e.is_client_error() => debug!("Rejected input: {e}"),
Self::Model(e) => error!("Prediction error: {e}"),
}
let (status, code) = self.status();
let mut response = create_error_response(status, code, &self.message(), None);
if matches!(self, Self::Busy) {
response.extensions_mut().insert(Busy);
}
response
}
#[cfg(feature = "grpc")]
pub(crate) fn into_status(self) -> tonic::Status {
grpc_status(self.status().0, self.message())
}
}
impl From<Refused> for InferError {
fn from(refused: Refused) -> Self {
match refused {
Refused::Busy => Self::Busy,
Refused::Lost => Self::Lost,
}
}
}
#[cfg(feature = "grpc")]
pub(crate) fn grpc_status(status: StatusCode, message: String) -> tonic::Status {
match status {
StatusCode::BAD_REQUEST => tonic::Status::invalid_argument(message),
StatusCode::NOT_FOUND => tonic::Status::not_found(message),
StatusCode::SERVICE_UNAVAILABLE => tonic::Status::resource_exhausted(message),
StatusCode::GATEWAY_TIMEOUT => tonic::Status::deadline_exceeded(message),
_ => tonic::Status::internal(message),
}
}
impl ServerState {
pub(super) async fn infer(
&self,
slot: &ModelSlot,
loaded: &Loaded,
announced: Option<Announced<'_>>,
inputs: Vec<HostTensor>,
) -> std::result::Result<Vec<HostTensor>, InferError> {
let start = Instant::now();
let permit = self
.inference_slots
.clone()
.try_acquire_owned()
.map_err(|_| InferError::Busy)?;
let task = self
.serving
.infer(&loaded.model.get_info().name, inputs, announced);
let answered = match self.request_timeout {
Some(timeout) => tokio::time::timeout(timeout, task)
.await
.map_err(|_| InferError::Timeout(timeout))?,
None => task.await,
};
drop(permit);
let outputs = answered?.map_err(InferError::Model)?;
let elapsed = start.elapsed();
slot.inference_latency.observe(elapsed);
self.inference_latency.observe(elapsed);
Ok(outputs)
}
pub(super) async fn infer_flat(
&self,
slot: &ModelSlot,
loaded: &Loaded,
announced: Option<Announced<'_>>,
data: Vec<f32>,
batch_size: usize,
) -> std::result::Result<Vec<f32>, InferError> {
let item = &loaded.model.get_info().input_spec.shape;
let mut shape = Vec::with_capacity(item.len() + 1);
shape.push(batch_size);
shape.extend_from_slice(item);
let input = HostTensor {
shape,
values: Values::F32(data),
};
let outputs = self.infer(slot, loaded, announced, vec![input]).await?;
match outputs.into_iter().next() {
Some(HostTensor {
values: Values::F32(values),
..
}) => Ok(values),
_ => Err(InferError::Model(
ModelError::InferenceFailed("the model returned no f32 output".to_string()).into(),
)),
}
}
pub(super) fn count_request(&self, elapsed: Duration, failed: bool, busy: bool) {
self.total_requests.fetch_add(1, Ordering::Relaxed);
self.request_latency.observe(elapsed);
if failed {
self.total_errors.fetch_add(1, Ordering::Relaxed);
}
if busy {
self.rejected_busy.fetch_add(1, Ordering::Relaxed);
}
}
}
pub(crate) fn f32s_from_le(bytes: &[u8]) -> Vec<f32> {
bytes
.as_chunks::<4>()
.0
.iter()
.map(|b| f32::from_le_bytes(*b))
.collect()
}
pub(crate) fn f32s_to_le(values: &[f32]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(size_of_val(values));
for value in values {
bytes.extend_from_slice(&value.to_le_bytes());
}
bytes
}
pub(crate) fn decode_raw(datatype: DataType, bytes: &[u8]) -> Values {
match datatype {
DataType::Fp32 => Values::F32(f32s_from_le(bytes)),
DataType::Int64 => Values::I64(
bytes
.as_chunks::<8>()
.0
.iter()
.map(|b| i64::from_le_bytes(*b))
.collect(),
),
DataType::Int32 => Values::I64(
bytes
.as_chunks::<4>()
.0
.iter()
.map(|b| i64::from(i32::from_le_bytes(*b)))
.collect(),
),
}
}
pub(crate) fn encode_raw(values: &Values) -> Vec<u8> {
match values {
Values::F32(v) => f32s_to_le(v),
Values::I64(v) => v.iter().flat_map(|x| x.to_le_bytes()).collect(),
}
}
pub(crate) fn values_datatype(values: &Values) -> DataType {
match values {
Values::F32(_) => DataType::Fp32,
Values::I64(_) => DataType::Int64,
}
}