Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2037b47
fix(discovery): fold the Qwen video-routing contract into mdcsum
glamr-agent Sep 10, 2026
1018be1
chore: remove factory working notes
glamr-agent Sep 10, 2026
ba58517
fix(llm): resolve the Qwen video contract across the WorkerSet
glamr-agent Sep 10, 2026
29461b4
fix(discovery): treat a contract-only card update as a change
glamr-agent Sep 10, 2026
0613b8d
docs(discovery): shorten video contract rationale
glamr-agent Sep 11, 2026
0fbe58c
docs(model-card): shorten contract test note
glamr-agent Sep 11, 2026
05dd4a6
style(discovery): deduplicate video contract comments
glamr-agent Sep 11, 2026
b899767
style(discovery): shorten video contract rationale
glamr-agent Sep 11, 2026
fbdfa7e
merge: update discovery contract fix from main
glamr-agent Sep 11, 2026
ea1ef0c
fix(discovery): retain groups during contract rebuilds
glamr-agent Sep 11, 2026
6386af0
Merge main into dyn-4390-mdcsum-qwen-video-contract-12b9e0090c8d
glamr-agent Sep 11, 2026
ef3a106
fix(llm): replace retained video contract worker sets
glamr-agent Sep 11, 2026
74c971d
style(llm): format retained group replacement
glamr-agent Sep 11, 2026
99fc315
Merge branch main into dyn-4390-mdcsum-qwen-video-contract-12b9e0090c8d
glamr-agent Sep 11, 2026
12eaa0b
fix(discovery): retain contract-only replacements
glamr-agent Sep 11, 2026
a30adb1
fix(discovery): preserve routing updates during replacement
glamr-agent Sep 11, 2026
39a67d2
style(discovery): format replacement target cleanup
glamr-agent Sep 11, 2026
6d82fd9
Merge branch main into dyn-4390-mdcsum-qwen-video-contract-12b9e0090c8d
glamr-agent Sep 11, 2026
877c7be
style(discovery): format video contract reconciliation
glamr-agent Sep 11, 2026
a5c8d87
style(discovery): wrap reconciliation formatting
glamr-agent Sep 11, 2026
cd725a3
fix(discovery): retain blocked status metadata
glamr-agent Sep 11, 2026
bf9bc49
style(discovery): normalize status match patterns
glamr-agent Sep 11, 2026
3f055fb
style(discovery): compact group commit call
glamr-agent Sep 12, 2026
1f0b0cc
fix(discovery): retain commits only across contract rebuilds
glamr-agent Sep 12, 2026
3723105
style(discovery): format rebuild admission guard
glamr-agent Sep 12, 2026
d6789bb
style(discovery): apply rustfmt
glamr-agent Sep 12, 2026
99baf27
style(discovery): format watcher replacement
glamr-agent Sep 12, 2026
5f2819e
fix(discovery): keep serving while a video-contract rebuild is prepared
glamr-agent Sep 14, 2026
25770fc
fix(discovery): recheck the retained contract during pending rebuilds
glamr-agent Sep 16, 2026
1d70837
merge: resolve Qwen video contract conflict with main
glamr-agent Sep 16, 2026
a67101f
Merge branch 'main' into dyn-4390-mdcsum-qwen-video-contract-12b9e009…
yunzhoul-nv Sep 16, 2026
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
20 changes: 1 addition & 19 deletions lib/llm/src/discovery/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ use crate::{
tensor::{NvCreateTensorRequest, NvCreateTensorResponse},
},
types::generic::realtime::{RealtimeClientEvent, RealtimeServerEvent},
utils::canonicalize_json,
worker_type::WorkerType,
};

Expand Down Expand Up @@ -1328,25 +1329,6 @@ fn lora_projection_fingerprint(card: &ModelDeploymentCard) -> anyhow::Result<Str
Ok(blake3::hash(&serde_json::to_vec(&value)?).to_string())
}

fn canonicalize_json(value: &mut serde_json::Value) {
match value {
serde_json::Value::Object(object) => {
let mut entries = std::mem::take(object).into_iter().collect::<Vec<_>>();
entries.sort_by(|left, right| left.0.cmp(&right.0));
for (key, mut value) in entries {
canonicalize_json(&mut value);
object.insert(key, value);
}
}
serde_json::Value::Array(values) => {
for value in values {
canonicalize_json(value);
}
}
_ => {}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
80 changes: 80 additions & 0 deletions lib/llm/src/model_card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,17 @@ impl ModelDeploymentCard {
bytes_to_hash.extend_from_slice(b"\0vllm_enable_tower_connector_lora\0true");
}

// Workers derive this contract from their own packages, so hash
// its content. Canonicalize: serde_json preserves key order.
if let Some(contract) = self.runtime_config.runtime_data.get(
Comment thread
glamr-agent marked this conversation as resolved.
Outdated
crate::local_model::runtime_config::VLLM_QWEN_VIDEO_PROCESSOR_CONTRACT_RUNTIME_KEY,
) {
let mut contract = contract.clone();
crate::utils::canonicalize_json(&mut contract);
bytes_to_hash.extend_from_slice(b"\0vllm_qwen_video_processor_contract\0");
bytes_to_hash.extend(blake3::hash(contract.to_string().as_bytes()).as_bytes());
}

// TODO: Do we want any other user_data or runtime_config?

blake3::hash(&bytes_to_hash).to_string()
Expand Down Expand Up @@ -3200,6 +3211,75 @@ mod ownership_tests {
assert_eq!(missing.mdcsum(), disabled.mdcsum());
assert_ne!(missing.mdcsum(), enabled.mdcsum());
}

/// The group builds one video-routing processor from a single
Comment thread
glamr-agent marked this conversation as resolved.
Outdated
/// representative card, so every member of a WorkerSet is served with the
/// representative's Qwen video contract. Cards whose contract differs -
/// including a card that withholds it because engine-level
/// `--mm-processor-kwargs` may change the video token layout - must
/// therefore hash apart.
///
/// Note: `mdcsum()` caches its result on first call via `OnceLock`, so each
/// case builds a fresh card rather than mutating one and re-hashing.
#[test]
fn qwen_video_processor_contract_isolates_worker_sets() {
use crate::local_model::runtime_config::VLLM_QWEN_VIDEO_PROCESSOR_CONTRACT_RUNTIME_KEY;

fn card_with_contract(contract: serde_json::Value) -> ModelDeploymentCard {
let mut card = ModelDeploymentCard::with_name_only("model");
card.runtime_config.runtime_data.insert(
VLLM_QWEN_VIDEO_PROCESSOR_CONTRACT_RUNTIME_KEY.to_string(),
contract,
);
card
}

fn legacy_ceil() -> serde_json::Value {
serde_json::json!({
"placeholder_target": "bare_video_token",
"resize_mode": "legacy_ceil",
})
}

let withheld = ModelDeploymentCard::with_name_only("model");
assert_ne!(
withheld.mdcsum(),
card_with_contract(legacy_ceil()).mdcsum(),
"a worker that never published the contract must not join the WorkerSet of one that did"
);

assert_ne!(
card_with_contract(legacy_ceil()).mdcsum(),
card_with_contract(serde_json::json!({
"placeholder_target": "bare_video_token",
"resize_mode": "smart_resize",
Comment thread
glamr-agent marked this conversation as resolved.
Outdated
}))
.mdcsum(),
"two workers publishing different contracts must not share a WorkerSet"
);

// serde_json preserves key order here, so the same contract written in
// a different key order must still hash the same.
assert_eq!(
card_with_contract(legacy_ceil()).mdcsum(),
card_with_contract(serde_json::json!({
"resize_mode": "legacy_ceil",
"placeholder_target": "bare_video_token",
}))
.mdcsum(),
"key order within the contract must not change the checksum"
);

// Negative control: a deployment that never carries this key is
// unaffected, so its cache directory and reported checksum do not move.
let bare = ModelDeploymentCard::with_name_only("model");
let mut unrelated = ModelDeploymentCard::with_name_only("model");
unrelated
.runtime_config
.runtime_data
.insert("some_unrelated_runtime_key".to_string(), true.into());
assert_eq!(bare.mdcsum(), unrelated.mdcsum());
}
}

#[cfg(test)]
Expand Down
25 changes: 25 additions & 0 deletions lib/llm/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,28 @@ pub mod lora;
pub mod zmq;

pub use lora::lora_name_to_id;

/// Sort every JSON object in `value` by key, recursively.
///
/// `serde_json` runs with `preserve_order` in this workspace, so an object
/// keeps the key order it was parsed in. Anything that hashes JSON as an
/// identity has to canonicalize first, or two logically identical values
/// hash apart.
pub(crate) fn canonicalize_json(value: &mut serde_json::Value) {
match value {
serde_json::Value::Object(object) => {
let mut entries = std::mem::take(object).into_iter().collect::<Vec<_>>();
entries.sort_by(|left, right| left.0.cmp(&right.0));
for (key, mut value) in entries {
canonicalize_json(&mut value);
object.insert(key, value);
}
}
serde_json::Value::Array(values) => {
for value in values {
canonicalize_json(value);
}
}
_ => {}
}
}
Loading