Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/bindings/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,11 @@ unsafe fn preprocess_request(
let priority_jump = extract_priority_jump(request.nvext.as_ref());
let strict_priority = extract_strict_priority(request.nvext.as_ref());
let routing_constraints = extract_routing_constraints(request.nvext.as_ref());
let (token_ids, _) = match preprocessor.gather_tokens(&request, None, None) {
let (token_ids, _) = match handles
.runtime
.secondary()
.block_on(preprocessor.gather_tokens(&request, None, None))
{
Ok(tokens) => tokens,
Err(e) => {
tracing::error!(error = ?e, "Failed to collect completion prompt tokens");
Expand Down
30 changes: 19 additions & 11 deletions lib/llm/src/preprocessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ use dynamo_runtime::metrics::frontend_perf::{
DETOKENIZE_TOKEN_COUNT, DETOKENIZE_TOTAL_US, STAGE_DURATION_SECONDS, STAGE_PREPROCESS,
StageGuard, TEMPLATE_SECONDS, TOKENIZE_SECONDS,
};
use std::borrow::Cow;
use std::{any::Any, collections::HashMap, pin::Pin, sync::Arc};
use tracing;

Expand Down Expand Up @@ -674,6 +673,7 @@ impl OpenAIPreprocessor {
let (token_ids, annotations) = {
let _nvtx = dynamo_nvtx_range!("preprocess.tokenize");
self.gather_tokens(request, formatted_prompt.as_deref(), tracker)
.await
.with_context(|| "Failed to gather tokens")?
};
TOKENIZE_SECONDS.observe(tokenize_start.elapsed().as_secs_f64());
Expand Down Expand Up @@ -1619,7 +1619,7 @@ impl OpenAIPreprocessor {
/// the caller asked for. The caller owns the result and is responsible for
/// installing it on the builder via `builder.token_ids(...)` once any
/// downstream consumers (e.g. MM-routing) have borrowed it.
pub fn gather_tokens<
pub async fn gather_tokens<
R: OAIChatLikeRequest
+ AnnotationsProvider
+ SamplingOptionsProvider
Expand Down Expand Up @@ -1702,10 +1702,10 @@ impl OpenAIPreprocessor {
tracing::warn!(
"backend_instance_id provided but no token_data; tokenizing prompt"
);
let encoding = self.encode_with_timing(prompt, tracker)?;
let encoding = self.encode_with_timing(prompt, tracker).await?;
(encoding.token_ids().to_vec(), false)
} else {
let encoding = self.encode_with_timing(prompt, tracker)?;
let encoding = self.encode_with_timing(prompt, tracker).await?;
(encoding.token_ids().to_vec(), false)
};

Expand All @@ -1723,7 +1723,7 @@ impl OpenAIPreprocessor {
}
TextInput::Batch(texts) => {
if texts.len() == 1 {
let encoding = self.encode_with_timing(&texts[0], tracker)?;
let encoding = self.encode_with_timing(&texts[0], tracker).await?;
let tokens = encoding.token_ids().to_vec();
token_count = Some(tokens.len());
tokens_out = tokens;
Expand Down Expand Up @@ -1770,19 +1770,25 @@ impl OpenAIPreprocessor {
Ok(())
}

fn encode_with_timing(
async fn encode_with_timing(
&self,
prompt: &str,
tracker: Option<&RequestTracker>,
) -> anyhow::Result<Encoding> {
let encode_start = Instant::now();
let prompt = if prompt.contains('\0') {
// Offload the CPU-heavy BPE encode to the bounded blocking pool instead of running it on
// the async event loop. For long prompts at high concurrency, a synchronous encode here
// stalls the frontend tokio runtime for seconds, starving the I/O tasks that share the
// runtime. Own the prompt + clone the tokenizer (Arc) so the closure is 'static + Send;
// mirrors the embedding path's spawn_blocking offload.
let owned = if prompt.contains('\0') {
tracing::debug!("Prompt contains null bytes; stripping to avoid tokenizer divergence");
Cow::Owned(prompt.replace('\0', ""))
prompt.replace('\0', "")
} else {
Cow::Borrowed(prompt)
prompt.to_string()
};
let encoding = self.tokenizer.encode(prompt.as_ref())?;
let tokenizer = self.tokenizer.clone();
let encoding = tokio::task::spawn_blocking(move || tokenizer.encode(&owned)).await??;
if let Some(t) = tracker {
t.record_tokenize_latency(encode_start.elapsed());
}
Expand Down Expand Up @@ -3197,7 +3203,9 @@ impl
} else {
// Normal path: tokenize the prompt; embeddings don't need MM routing,
// so install tokens on the builder right away.
let (token_ids, ann) = self.gather_tokens(&request, None, tracker.as_deref())?;
let (token_ids, ann) = self
.gather_tokens(&request, None, tracker.as_deref())
.await?;
builder.token_ids(token_ids);
ann
};
Expand Down
Loading