fn qwen35_raw_tokens(
state: &AppState,
prompt: &str,
max_tokens: usize,
temperature: f32,
cancel: &CancelToken,
) -> Result<Option<(Vec<u32>, usize)>, ApiErr> {
use crate::gguf::QuantizedGenerateConfig;
let Some(session) = state.qwen35_session() else {
return Ok(None);
};
let Some(mapped) = state.mapped_gguf_model() else {
return Err(api_err(
StatusCode::INTERNAL_SERVER_ERROR,
"the Qwen3.5 session has no retained GGUF to tokenize with (#3571)",
));
};
let input_ids = mapped.model.encode(prompt).unwrap_or_default();
if input_ids.is_empty() {
return Err(api_err(StatusCode::BAD_REQUEST, "Prompt cannot be empty"));
}
let prompt_len = input_ids.len();
if prompt_len >= session.context_length {
return Err(api_err(
StatusCode::BAD_REQUEST,
format!(
"the prompt is {prompt_len} tokens and this model declares a context of {}: \
it was refused whole rather than truncated (#3571)",
session.context_length
),
));
}
let stop_tokens: Vec<u32> = state.model_eos_token_id().into_iter().collect();
let gen_config = QuantizedGenerateConfig {
max_tokens: max_tokens.min(session.context_length - prompt_len),
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()
};
let mut s = session.session.lock().map_err(|_| {
api_err(
StatusCode::INTERNAL_SERVER_ERROR,
"the Qwen3.5 session is unusable: an earlier request panicked while holding it (#3571)",
)
})?;
let turn = s.generate(&input_ids, &gen_config, &mut |_| true);
session
.on_gpu
.store(s.on_gpu(), std::sync::atomic::Ordering::Relaxed);
let mut tokens = turn
.map_err(|e| {
state.metrics.record_failure();
api_err(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Qwen3.5 generation failed: {e}"),
)
})?
.tokens;
if tokens.len() > prompt_len && tokens.last().is_some_and(|t| stop_tokens.contains(t)) {
tokens.pop();
}
Ok(Some((tokens, prompt_len)))
}
fn try_qwen35_generate(
state: &AppState,
request: &GenerateRequest,
cancel: &CancelToken,
) -> Result<Option<GenerateResponse>, ApiErr> {
let Some((tokens, prompt_len)) = qwen35_raw_tokens(
state,
&request.prompt,
request.max_tokens,
request.temperature,
cancel,
)?
else {
return Ok(None);
};
let Some(mapped) = state.mapped_gguf_model() else {
return Ok(None);
};
let generated = tokens[prompt_len.min(tokens.len())..].to_vec();
Ok(Some(GenerateResponse {
text: mapped.model.decode(&generated),
num_generated: generated.len(),
token_ids: tokens,
}))
}
fn try_qwen35_batch_generate(
state: &AppState,
request: &BatchGenerateRequest,
cancel: &CancelToken,
) -> Result<Option<Vec<GenerateResponse>>, ApiErr> {
if state.qwen35_session().is_none() {
return Ok(None);
}
let mut results = Vec::with_capacity(request.prompts.len());
for prompt in &request.prompts {
let one = GenerateRequest {
prompt: prompt.clone(),
max_tokens: request.max_tokens,
temperature: request.temperature,
strategy: request.strategy.clone(),
top_k: request.top_k,
top_p: request.top_p,
seed: request.seed,
model_id: None,
};
match try_qwen35_generate(state, &one, cancel)? {
Some(r) => results.push(r),
None => return Ok(None),
}
}
Ok(Some(results))
}
fn try_qwen35_stream_tokens(
state: &AppState,
request: &GenerateRequest,
cancel: &CancelToken,
) -> Result<Option<(Vec<u32>, usize, std::sync::Arc<BPETokenizer>)>, ApiErr> {
let Some((tokens, prompt_len)) = qwen35_raw_tokens(
state,
&request.prompt,
request.max_tokens,
request.temperature,
cancel,
)?
else {
return Ok(None);
};
let tokenizer = state
.get_tokenizer(None)
.map_err(|e| api_err(super::model_resolution_status(&e), e.to_string()))?;
Ok(Some((tokens, prompt_len, tokenizer)))
}
#[cfg(test)]
mod qwen35_raw_generate_tests {
use super::*;
#[test]
fn a_state_without_a_hybrid_leaves_the_raw_chain_unchanged() {
let state = AppState::demo().expect("demo state");
assert!(
state.qwen35_session().is_none(),
"the fixture must hold no hybrid"
);
let cancel = CancelToken::new();
let one: GenerateRequest =
serde_json::from_value(serde_json::json!({"prompt": "hi", "max_tokens": 4}))
.expect("request");
let many: BatchGenerateRequest =
serde_json::from_value(serde_json::json!({"prompts": ["hi"], "max_tokens": 4}))
.expect("request");
assert!(matches!(
try_qwen35_generate(&state, &one, &cancel),
Ok(None)
));
assert!(matches!(
try_qwen35_batch_generate(&state, &many, &cancel),
Ok(None)
));
assert!(matches!(
try_qwen35_stream_tokens(&state, &one, &cancel),
Ok(None)
));
}
}
#[cfg(test)]
mod raw_stream_utf8_tests {
use super::*;
fn tok() -> BPETokenizer {
let vocab = ["<unk>", "caf", "<0xC3>", "<0xA9>", "Ġquick"];
BPETokenizer::new(
vocab.iter().map(|s| (*s).to_string()).collect(),
vec![],
"<unk>",
)
.expect("test tokenizer")
}
#[test]
fn a_multi_byte_character_streams_whole() {
let events = raw_stream_token_events(&tok(), &[1, 2, 3, 4]);
let texts: Vec<&str> = events.iter().map(|e| e.text.as_str()).collect();
assert_eq!(texts.concat(), "café quick", "events: {texts:?}");
assert!(
texts.iter().all(|t| !t.contains('\u{FFFD}')),
"events: {texts:?}"
);
assert_eq!(events.last().map(|e| e.token_id), Some(4));
}
#[test]
fn an_unfinished_character_at_the_end_is_flushed_not_dropped() {
let events = raw_stream_token_events(&tok(), &[1, 2]);
let text: String = events.iter().map(|e| e.text.as_str()).collect();
assert_eq!(text, "caf\u{FFFD}");
}
}