use serde_json::Value;
use crate::ApiError;
const LIMIT: f32 = 100.0;
#[derive(Debug, Clone, Default, PartialEq)]
pub(crate) struct LogitBias {
entries: Vec<(usize, f32)>,
}
impl Eq for LogitBias {}
impl std::hash::Hash for LogitBias {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
for (id, bias) in &self.entries {
id.hash(state);
bias.to_bits().hash(state);
}
}
}
impl LogitBias {
pub(crate) fn parse(value: Option<&Value>, route: &str) -> Result<Self, ApiError> {
let Some(value) = value else {
return Ok(LogitBias::default());
};
if value.is_null() {
return Ok(LogitBias::default());
}
let Some(map) = value.as_object() else {
return Err(crate::invalid_request(
&format!("`logit_bias` on {route} must be an object of token id to bias"),
"logit_bias",
));
};
let mut entries = Vec::with_capacity(map.len());
for (key, raw) in map {
let id: usize = key.parse().map_err(|_| {
crate::invalid_request(
&format!("`logit_bias` key {key:?} is not a token id"),
"logit_bias",
)
})?;
let bias = raw.as_f64().ok_or_else(|| {
crate::invalid_request(
&format!("`logit_bias[{key}]` is not a number"),
"logit_bias",
)
})?;
if !bias.is_finite() || bias.abs() > LIMIT as f64 {
return Err(crate::invalid_request(
&format!(
"`logit_bias[{key}]` is {bias}, outside the range -{LIMIT} to {LIMIT}"
),
"logit_bias",
));
}
entries.push((id, bias as f32));
}
entries.sort_unstable_by_key(|(id, _)| *id);
entries.dedup_by_key(|(id, _)| *id);
Ok(LogitBias { entries })
}
pub(crate) fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub(crate) fn apply(&self, scores: &mut [f32]) {
for (id, bias) in &self.entries {
if let Some(score) = scores.get_mut(*id) {
*score += *bias;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(v: serde_json::Value) -> LogitBias {
LogitBias::parse(Some(&v), "/t").expect("valid")
}
#[test]
fn a_bias_shifts_only_the_named_tokens() {
let bias = parse(serde_json::json!({"1": 5.0, "3": -2.5}));
let mut scores = vec![0.0f32; 4];
bias.apply(&mut scores);
assert_eq!(scores, vec![0.0, 5.0, 0.0, -2.5]);
}
#[test]
fn a_bias_cannot_lift_a_masked_token() {
let bias = parse(serde_json::json!({"0": 100.0}));
let mut scores = vec![f32::NEG_INFINITY, 0.0];
bias.apply(&mut scores);
assert_eq!(scores[0], f32::NEG_INFINITY, "a mask was lifted by a bias");
}
#[test]
fn the_empty_forms_are_accepted_and_change_nothing() {
for v in [serde_json::json!({}), serde_json::Value::Null] {
let bias = LogitBias::parse(Some(&v), "/t").expect("accepted");
assert!(bias.is_empty());
}
assert!(LogitBias::parse(None, "/t").expect("accepted").is_empty());
}
#[test]
fn a_bias_outside_the_range_is_a_bad_request() {
for v in [
serde_json::json!({"1": 101.0}),
serde_json::json!({"1": -100.5}),
] {
let err = LogitBias::parse(Some(&v), "/t").expect_err("refused");
assert_eq!(err.0, axum::http::StatusCode::BAD_REQUEST);
}
}
#[test]
fn a_key_that_is_not_a_token_id_is_a_bad_request() {
let err =
LogitBias::parse(Some(&serde_json::json!({"abc": 1.0})), "/t").expect_err("refused");
assert_eq!(err.0, axum::http::StatusCode::BAD_REQUEST);
}
#[test]
fn an_out_of_range_id_does_not_panic() {
let bias = parse(serde_json::json!({"99": 10.0}));
let mut scores = vec![0.0f32; 4];
bias.apply(&mut scores);
assert_eq!(scores, vec![0.0; 4]);
}
}