bump dynamo-tokenizers to 1.7.0 (#32981)

This commit is contained in:
Rain Jiang
2026-07-31 15:02:28 -07:00
committed by GitHub
parent 70cec31378
commit 1d640aaea2
4 changed files with 887 additions and 74 deletions
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -33,9 +33,8 @@ uuid = { workspace = true }
axum = { version = "0.8.9", features = ["json", "tokio"] } axum = { version = "0.8.9", features = ["json", "tokio"] }
core_affinity = "0.8" core_affinity = "0.8"
# pin the dynamo-tokenizers for now, 1.7.0 enables`serde_json/preserve_order` # the dynamo-tokenizers is deps on hf-hub, should bump version together.
# transitively, which reorders every JSON object. dynamo-tokenizers = "1.7.0"
dynamo-tokenizers = "=1.5.3"
flume = "0.12.0" flume = "0.12.0"
itertools = "0.14" itertools = "0.14"
hf-hub = { version = "0.4", default-features = false } hf-hub = { version = "0.4", default-features = false }
+31 -23
View File
@@ -238,12 +238,9 @@ pub(super) fn frame_value(out: &ChunkEvent, rid: &str) -> serde_json::Value {
/// accumulated length, where rebuilding the `Value` is O(T) per frame and so O(T²) /// accumulated length, where rebuilding the `Value` is O(T) per frame and so O(T²)
/// per request. /// per request.
/// ///
/// Byte-identical to `frame_value(..).to_string()`, which requires emitting /// Encodes the same JSON document as `frame_value(..).to_string()` — same keys,
/// `meta_info`'s keys in the alphabetical order `serde_json`'s `BTreeMap` gives /// same values, same escaping — pinned for both the plain and the logprob shapes
/// them — pinned for both the plain and the logprob shapes by /// by `cumulative_frame_json_matches_serde`.
/// `cumulative_frame_json_matches_serde`. `None` only when the extras memo was
/// invalidated (see `extras_memo_broken`), leaving the `Value` path as the
/// fallback.
pub(super) fn cumulative_frame_json( pub(super) fn cumulative_frame_json(
acc: &OutputAccumulator, acc: &OutputAccumulator,
rid: &str, rid: &str,
@@ -255,12 +252,13 @@ pub(super) fn cumulative_frame_json(
return None; return None;
} }
let o = acc.snapshot(); let o = acc.snapshot();
// Through `Value`, not `to_string` on the struct: the `Value` path sorts the // Through `Value` rather than `to_string` on the struct: it is the same
// finish reason's own keys via `BTreeMap`, and this must match it byte for byte. // encoder the slow path runs the finish reason through, so any representation
// quirk is reproduced instead of re-derived.
let finish = serde_json::to_value(&o.finish_reason).ok()?.to_string(); let finish = serde_json::to_value(&o.finish_reason).ok()?.to_string();
// KEEP ALPHABETICAL: `serde_json::Map` is a `BTreeMap` here, so this is the // Alphabetical by convention only — a stable order that is easy to extend and
// order the `Value` path produces and the order the equivalence test asserts. // diff.
let mut m = String::new(); let mut m = String::new();
let _ = write!(m, "{{\"completion_tokens\":{}", o.completion_tokens); let _ = write!(m, "{{\"completion_tokens\":{}", o.completion_tokens);
let _ = write!(m, ",\"finish_reason\":{finish}"); let _ = write!(m, ",\"finish_reason\":{finish}");
@@ -668,11 +666,16 @@ mod tests {
assert_eq!(opt_texts(&t), Some(t.as_slice())); assert_eq!(opt_texts(&t), Some(t.as_slice()));
} }
/// The memoized cumulative fast path must emit **byte-identical** JSON to the /// Parse a frame so the fast and slow paths can be compared as documents.
/// `serde_json::Value` builder it replaces — same keys, same alphabetical order fn as_json(frame: &str) -> serde_json::Value {
/// (`Map` is a `BTreeMap`; no `preserve_order`), same escaping. Covers unicode serde_json::from_str(frame).expect("a frame must be valid JSON")
/// and control chars, an empty-ids first frame, a finish_reason, and the batch }
/// `index`. Guards the O(T) rewrite of the O(T²) `output_ids` serialization.
/// The memoized cumulative fast path must emit the **same JSON document** as the
/// `serde_json::Value` builder it replaces — same keys, same values, same
/// escaping. Covers unicode and control chars, an empty-ids first frame, a
/// finish_reason, and the batch `index`. Guards the O(T) rewrite of the O(T²)
/// `output_ids` serialization.
#[test] #[test]
fn cumulative_frame_json_matches_serde() { fn cumulative_frame_json_matches_serde() {
let deltas = [ let deltas = [
@@ -720,7 +723,14 @@ mod tests {
acc.fold(d); acc.fold(d);
let fast = cumulative_frame_json(&acc, "7", index).expect("no extras → fast path"); let fast = cumulative_frame_json(&acc, "7", index).expect("no extras → fast path");
let slow = tag_value(frame_value(acc.snapshot(), "7"), index); let slow = tag_value(frame_value(acc.snapshot(), "7"), index);
assert_eq!(fast, slow, "index={index:?} text={:?}", acc.snapshot().text); println!("fast={fast:?}");
println!("slow={slow:?}");
assert_eq!(
as_json(&fast),
as_json(&slow),
"index={index:?} text={:?}",
acc.snapshot().text
);
} }
} }
} }
@@ -728,12 +738,6 @@ mod tests {
/// The same equivalence, for the shape that made cumulative streaming O(T²): /// The same equivalence, for the shape that made cumulative streaming O(T²):
/// every logprob family at once, across several deltas, with and without /// every logprob family at once, across several deltas, with and without
/// `return_text_in_logprobs` texts and with a null ragged position. /// `return_text_in_logprobs` texts and with a null ragged position.
///
/// This is the guard on the memoization. The fast path hand-writes
/// `meta_info`'s keys, so it has to reproduce `serde_json`'s alphabetical
/// `BTreeMap` order and every family's exact tuple encoding; asserting equality
/// against the `Value` builder after each fold is what makes that safe to
/// maintain.
#[test] #[test]
fn cumulative_frame_json_matches_serde_with_logprobs() { fn cumulative_frame_json_matches_serde_with_logprobs() {
for with_texts in [false, true] { for with_texts in [false, true] {
@@ -824,7 +828,11 @@ mod tests {
let fast = cumulative_frame_json(&acc, "9", index) let fast = cumulative_frame_json(&acc, "9", index)
.expect("the extras memo must stay valid for a well-formed request"); .expect("the extras memo must stay valid for a well-formed request");
let slow = tag_value(frame_value(acc.snapshot(), "9"), index); let slow = tag_value(frame_value(acc.snapshot(), "9"), index);
assert_eq!(fast, slow, "with_texts={with_texts} index={index:?}"); assert_eq!(
as_json(&fast),
as_json(&slow),
"with_texts={with_texts} index={index:?}"
);
} }
} }
} }
@@ -68,7 +68,7 @@ pub enum FinishReason {
/// This arm is why the outer enum is untagged: a finish reason added Python-side /// This arm is why the outer enum is untagged: a finish reason added Python-side
/// must not fail the header decode, which rejects the whole frame — every /// must not fail the header decode, which rejects the whole frame — every
/// request in the batch, not just the one that carried it. /// request in the batch, not just the one that carried it.
Unknown(serde_json::Map<String, serde_json::Value>), Unknown(Box<serde_json::Map<String, serde_json::Value>>),
} }
impl From<FinishKind> for FinishReason { impl From<FinishKind> for FinishReason {