struct Qwen35CompletionPlan {
session: std::sync::Arc<crate::api::Qwen35Served>,
mapped: std::sync::Arc<crate::gguf::MappedGGUFModel>,
input_ids: Vec<u32>,
prompt_tokens: usize,
budget: usize,
stop_tokens: Vec<u32>,
gen_config: crate::gguf::QuantizedGenerateConfig,
}
fn qwen35_completion_plan(
state: &AppState,
request: &CompletionRequest,
max_tokens: usize,
temperature: f32,
cancel: &CancelToken,
) -> Result<Option<Qwen35CompletionPlan>, RErr> {
use crate::gguf::QuantizedGenerateConfig;
let Some(session) = state.qwen35_session() else {
return Ok(None);
};
let Some(mapped) = state.mapped_gguf_model() else {
return Err(rerr(
state,
StatusCode::INTERNAL_SERVER_ERROR,
"the Qwen3.5 session has no retained GGUF to tokenize with (#3571)",
));
};
let input_ids = mapped.model.encode(&request.prompt).unwrap_or_default();
if input_ids.is_empty() {
return Err(rerr(
state,
StatusCode::BAD_REQUEST,
"Prompt cannot be empty",
));
}
let prompt_tokens = input_ids.len();
let context_length = session.context_length;
if prompt_tokens >= context_length {
return Err(rerr(
state,
StatusCode::BAD_REQUEST,
format!(
"the prompt is {prompt_tokens} tokens and this model declares a context of \
{context_length}: it was refused whole rather than truncated (#3571)"
),
));
}
let budget = max_tokens.min(context_length - prompt_tokens);
let stop_tokens: Vec<u32> = state.model_eos_token_id().into_iter().collect();
let gen_config = QuantizedGenerateConfig {
max_tokens: budget,
temperature,
top_k: crate::infer::sampling_top_k(temperature, None),
stop_tokens: stop_tokens.clone(),
trace: state.is_trace_enabled(),
cancel: cancel.clone(),
..Default::default()
};
Ok(Some(Qwen35CompletionPlan {
session,
mapped,
input_ids,
prompt_tokens,
budget,
stop_tokens,
gen_config,
}))
}
pub(crate) async fn try_qwen35_completions(
state: &AppState,
request: &CompletionRequest,
max_tokens: usize,
temperature: f32,
start: std::time::Instant,
cancel: &CancelToken,
) -> Result<Option<CompletionResponse>, RErr> {
let Some(Qwen35CompletionPlan {
session,
mapped,
input_ids,
prompt_tokens,
budget,
stop_tokens,
gen_config,
}) = qwen35_completion_plan(state, request, max_tokens, temperature, cancel)?
else {
return Ok(None);
};
let decode_mapped = mapped.clone();
let turn = tokio::task::spawn_blocking(move || match session.session.lock() {
Ok(mut s) => {
let r = s.generate(&input_ids, &gen_config, &mut |_| true);
session
.on_gpu
.store(s.on_gpu(), std::sync::atomic::Ordering::Relaxed);
r.map_err(|e| e.to_string())
},
Err(_) => Err(POISONED.to_string()),
})
.await;
let turn = match turn {
Ok(Ok(turn)) => turn,
Ok(Err(e)) => {
state.metrics.record_failure();
return Err(rerr(
state,
StatusCode::INTERNAL_SERVER_ERROR,
format!("Qwen3.5 generation failed: {e}"),
));
},
Err(e) => {
state.metrics.record_failure();
return Err(rerr(
state,
StatusCode::INTERNAL_SERVER_ERROR,
format!("Qwen3.5 generation task failed: {e}"),
));
},
};
let mut generated_ids = turn.tokens[prompt_tokens..].to_vec();
if generated_ids
.last()
.is_some_and(|t| stop_tokens.contains(t))
{
generated_ids.pop();
}
let completion_tokens = generated_ids.len();
let text = decode_mapped.model.decode(&generated_ids);
state
.metrics
.record_success(completion_tokens, start.elapsed());
Ok(Some(completion_resp(
"cmpl-qwen35",
request.model.clone(),
text,
prompt_tokens,
completion_tokens,
budget,
request.stop.as_deref(),
state
.qwen35_session()
.map(|s| s.on_gpu.load(std::sync::atomic::Ordering::Relaxed)),
)))
}
const POISONED: &str =
"the Qwen3.5 session is unusable: an earlier request panicked while holding it (#3571)";
enum Qwen35StreamMsg {
Delta(String),
Done {
finish_reason: String,
completion_tokens: usize,
},
Failed(String),
}
pub(crate) fn qwen35_stream_delta(
decoded: &str,
emitted: usize,
stops: &[String],
) -> Option<String> {
if decoded.ends_with('\u{FFFD}') || !decoded.is_char_boundary(emitted.min(decoded.len())) {
return None;
}
let hold = stops
.iter()
.map(|s| s.len().saturating_sub(1))
.max()
.unwrap_or(0);
let mut safe = decoded.len().saturating_sub(hold);
while !decoded.is_char_boundary(safe) {
safe -= 1;
}
(safe > emitted).then(|| decoded[emitted..safe].to_string())
}
pub(crate) fn try_qwen35_completions_stream(
state: &AppState,
request: &CompletionRequest,
cancel: &CancelToken,
) -> Result<Option<axum::response::Response>, RErr> {
use axum::response::sse::{Event, Sse};
use axum::response::IntoResponse;
let max_tokens = request.max_tokens.unwrap_or(256);
let temperature = request.temperature.unwrap_or(0.7) as f32;
let Some(plan) = qwen35_completion_plan(state, request, max_tokens, temperature, cancel)?
else {
return Ok(None);
};
let start = std::time::Instant::now();
let stops: Vec<String> = request.stop.clone().unwrap_or_default();
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<Qwen35StreamMsg>();
let Qwen35CompletionPlan {
session,
mapped,
input_ids,
prompt_tokens,
budget,
stop_tokens,
gen_config,
} = plan;
tokio::task::spawn_blocking(move || {
let Ok(mut s) = session.session.lock() else {
let _ = tx.send(Qwen35StreamMsg::Failed(POISONED.to_string()));
return;
};
let mut generated: Vec<u32> = Vec::new();
let mut emitted = 0usize;
let result = s.generate(&input_ids, &gen_config, &mut |token| {
if stop_tokens.contains(&token) {
return true; }
generated.push(token);
let decoded = mapped.model.decode(&generated);
if stops.iter().any(|stop| decoded.contains(stop.as_str())) {
return false; }
if let Some(delta) = qwen35_stream_delta(&decoded, emitted, &stops) {
emitted += delta.len();
if tx.send(Qwen35StreamMsg::Delta(delta)).is_err() {
return false; }
}
true
});
session
.on_gpu
.store(s.on_gpu(), std::sync::atomic::Ordering::Relaxed);
drop(s);
if let Err(e) = result {
let _ = tx.send(Qwen35StreamMsg::Failed(format!(
"Qwen3.5 generation failed: {e}"
)));
return;
}
let completion_tokens = generated.len();
let (text, finish) = apply_stop_sequences(
mapped.model.decode(&generated),
Some(stops.as_slice()),
completion_tokens,
budget,
);
if text.len() > emitted && text.is_char_boundary(emitted) {
let _ = tx.send(Qwen35StreamMsg::Delta(text[emitted..].to_string()));
}
let _ = tx.send(Qwen35StreamMsg::Done {
finish_reason: finish.as_str().to_string(),
completion_tokens,
});
});
let id = format!("cmpl-qwen35-{}", epoch_millis());
let created = epoch_secs();
let model = request.model.clone();
let metrics = state.metrics.clone();
let chunk =
move |text: String, finish_reason: Option<String>, usage: Option<Usage>| CompletionChunk {
id: id.clone(),
object: "text_completion".to_string(),
created,
model: model.clone(),
choices: vec![CompletionChunkChoice {
text,
index: 0,
logprobs: None,
finish_reason,
}],
usage,
};
let events = async_stream::stream! {
while let Some(msg) = rx.recv().await {
let data = match msg {
Qwen35StreamMsg::Delta(text) => serde_json::to_string(&chunk(text, None, None)),
Qwen35StreamMsg::Done { finish_reason, completion_tokens } => {
metrics.record_success(completion_tokens, start.elapsed());
serde_json::to_string(&chunk(
String::new(),
Some(finish_reason),
Some(Usage {
prompt_tokens,
completion_tokens,
total_tokens: prompt_tokens + completion_tokens,
}),
))
},
Qwen35StreamMsg::Failed(e) => {
metrics.record_failure();
serde_json::to_string(&serde_json::json!({ "error": e }))
},
};
if let Ok(data) = data {
yield Ok::<Event, std::convert::Infallible>(Event::default().data(data));
}
}
yield Ok(Event::default().data("[DONE]"));
};
Ok(Some(Sse::new(events).into_response()))
}