From 88f8bca65792f3fba48a878226a991665e66d1b5 Mon Sep 17 00:00:00 2001 From: Omer Aplatony Date: Tue, 14 Jul 2026 12:14:40 +0300 Subject: [PATCH 1/6] feat: set ownerReference on ModelMetadata CRs for automatic GC on pod deletion Signed-off-by: Omer Aplatony --- .../python/modelexpress/client.py | 6 ++++++ .../python/modelexpress/envs.py | 4 ++++ .../metadata/k8s_service_client.py | 2 ++ .../python/modelexpress/metadata/publish.py | 14 ++++++++++++- modelexpress_common/proto/p2p.proto | 9 +++++++++ modelexpress_common/src/envs.rs | 6 ++++++ modelexpress_server/src/p2p/backend.rs | 2 ++ .../src/p2p/backend/kubernetes.rs | 17 ++++++++++++++++ modelexpress_server/src/p2p/backend/memory.rs | 20 ++++++++++--------- modelexpress_server/src/p2p/backend/redis.rs | 2 ++ modelexpress_server/src/p2p/service.rs | 12 ++++++++--- modelexpress_server/src/p2p/state.rs | 14 ++++++++----- 12 files changed, 90 insertions(+), 18 deletions(-) diff --git a/modelexpress_client/python/modelexpress/client.py b/modelexpress_client/python/modelexpress/client.py index c48ab65b..0cbb5c8b 100644 --- a/modelexpress_client/python/modelexpress/client.py +++ b/modelexpress_client/python/modelexpress/client.py @@ -46,6 +46,8 @@ def publish_metadata( identity: "p2p_pb2.SourceIdentity", worker: "p2p_pb2.WorkerMetadata", worker_id: str, + pod_name: str = "", + pod_uid: str = "", ) -> str: """Publish worker metadata and return the computed mx_source_id.""" @@ -167,6 +169,8 @@ def publish_metadata( identity: "p2p_pb2.SourceIdentity", worker: "p2p_pb2.WorkerMetadata", worker_id: str, + pod_name: str = "", + pod_uid: str = "", ) -> str: """Publish metadata for one worker so targets can discover this source. @@ -176,6 +180,8 @@ def publish_metadata( identity=identity, worker=worker, worker_id=worker_id, + pod_name=pod_name, + pod_uid=pod_uid, ) response = self.stub.PublishMetadata(request, timeout=30) if not response.success: diff --git a/modelexpress_client/python/modelexpress/envs.py b/modelexpress_client/python/modelexpress/envs.py index 64e1b27d..8e3f242c 100644 --- a/modelexpress_client/python/modelexpress/envs.py +++ b/modelexpress_client/python/modelexpress/envs.py @@ -105,6 +105,8 @@ VLLM_ATTENTION_BACKEND: str HOSTNAME: str POD_NAMESPACE: str + POD_NAME: str + POD_UID: str _TRUTHY = {"1", "true", "yes", "on"} @@ -213,6 +215,8 @@ def _env_float(name: str, default: float) -> float: "VLLM_ATTENTION_BACKEND": lambda: os.environ.get("VLLM_ATTENTION_BACKEND", "auto"), "HOSTNAME": lambda: os.environ.get("HOSTNAME", ""), "POD_NAMESPACE": lambda: os.environ.get("POD_NAMESPACE", ""), + "POD_NAME": lambda: os.environ.get("POD_NAME",""), + "POD_UID": lambda: os.environ.get("POD_UID","") } diff --git a/modelexpress_client/python/modelexpress/metadata/k8s_service_client.py b/modelexpress_client/python/modelexpress/metadata/k8s_service_client.py index 886321f5..d4259997 100644 --- a/modelexpress_client/python/modelexpress/metadata/k8s_service_client.py +++ b/modelexpress_client/python/modelexpress/metadata/k8s_service_client.py @@ -104,6 +104,8 @@ def publish_metadata( identity: "p2p_pb2.SourceIdentity", worker: "p2p_pb2.WorkerMetadata", worker_id: str, + pod_name: str = "", + pod_uid: str = "", ) -> str: """Compute mx_source_id locally - there is no central store to hit. diff --git a/modelexpress_client/python/modelexpress/metadata/publish.py b/modelexpress_client/python/modelexpress/metadata/publish.py index 52c7a476..c999a024 100644 --- a/modelexpress_client/python/modelexpress/metadata/publish.py +++ b/modelexpress_client/python/modelexpress/metadata/publish.py @@ -127,6 +127,9 @@ def publish_metadata_and_ready( f"[Worker {worker_rank}] Preparing {len(tensors)} tensors for model '{identity.model_name}'" ) + pod_name = envs.POD_NAME + pod_uid = envs.POD_UID + tensor_protos = build_tensor_protos(tensors, device_id, worker_rank) if _is_p2p_metadata_enabled(mx_client): @@ -171,6 +174,8 @@ def publish_fn() -> str: worker=worker, worker_id=worker_id, worker_rank=worker_rank, + pod_name=pod_name, + pod_uid=pod_uid, ) grpc_server.set_mx_source_id(mx_source_id) logger.info( @@ -204,6 +209,8 @@ def publish_fn() -> str: worker=worker, worker_id=worker_id, worker_rank=worker_rank, + pod_name=pod_name, + pod_uid=pod_uid, ) logger.info( f"[Worker {worker_rank}] Published metadata to MX server " @@ -231,13 +238,18 @@ def _publish_metadata_to_server( worker: "p2p_pb2.WorkerMetadata", worker_id: str, worker_rank: int, + pod_name: str = "", + pod_uid: str = "", ) -> str: """Publish metadata with bounded retries and exponential backoff.""" last_error: grpc.RpcError | None = None for attempt in range(1, PUBLISH_METADATA_MAX_ATTEMPTS + 1): try: - return mx_client.publish_metadata(identity, worker, worker_id) + return mx_client.publish_metadata( + identity, worker, worker_id, + pod_name=pod_name, pod_uid=pod_uid, + ) except grpc.RpcError as exc: if exc.code() not in PUBLISH_METADATA_RETRYABLE_STATUS_CODES: raise diff --git a/modelexpress_common/proto/p2p.proto b/modelexpress_common/proto/p2p.proto index 85ecd9d7..435e84db 100644 --- a/modelexpress_common/proto/p2p.proto +++ b/modelexpress_common/proto/p2p.proto @@ -499,6 +499,15 @@ message PublishMetadataRequest { // Differentiates multiple replicas with identical SourceIdentity. // If empty, the server rejects the request. string worker_id = 3; + + // Kubernetes pod name of the calling worker, set via the downward API. + // Used with pod_uid to set an ownerReference on the ModelMetadata CR + // so K8s garbage-collects it when the pod is deleted. + // When empty, no ownerReference is set. + string pod_name = 4; + + // Kubernetes pod UID of the calling worker, set via the downward API. + string pod_uid = 5; } message PublishMetadataResponse { diff --git a/modelexpress_common/src/envs.rs b/modelexpress_common/src/envs.rs index 33520b91..efab43da 100644 --- a/modelexpress_common/src/envs.rs +++ b/modelexpress_common/src/envs.rs @@ -107,6 +107,10 @@ pub const REDIS_PORT: &str = "REDIS_PORT"; pub const MX_METADATA_NAMESPACE: &str = "MX_METADATA_NAMESPACE"; /// Kubernetes namespace injected via the downward API for in-cluster pods. pub const POD_NAMESPACE: &str = "POD_NAMESPACE"; +/// Kubernetes pod name injected via the downward API (used by clients). +pub const POD_NAME: &str = "POD_NAME"; +/// Kubernetes pod UID injected via the downward API (used by clients). +pub const POD_UID: &str = "POD_UID"; // ── Reaper (server) ───────────────────────────────────────────────────────── /// Interval (seconds) between reaper scans for stale/GC worker sweeps. @@ -324,6 +328,8 @@ mod tests { assert_eq!(HOME, "HOME"); assert_eq!(USERPROFILE, "USERPROFILE"); assert_eq!(KUBECONFIG, "KUBECONFIG"); + assert_eq!(POD_NAME, "POD_NAME"); + assert_eq!(POD_UID, "POD_UID"); } #[test] diff --git a/modelexpress_server/src/p2p/backend.rs b/modelexpress_server/src/p2p/backend.rs index fe1a05de..c00f6408 100644 --- a/modelexpress_server/src/p2p/backend.rs +++ b/modelexpress_server/src/p2p/backend.rs @@ -296,6 +296,8 @@ pub trait MetadataBackend: Send + Sync { identity: &SourceIdentity, worker_id: &str, worker: WorkerMetadata, + pod_name: &str, + pod_uid: &str, ) -> MetadataResult<()>; /// Get full tensor metadata for one specific worker. diff --git a/modelexpress_server/src/p2p/backend/kubernetes.rs b/modelexpress_server/src/p2p/backend/kubernetes.rs index a592492f..340cb6e0 100644 --- a/modelexpress_server/src/p2p/backend/kubernetes.rs +++ b/modelexpress_server/src/p2p/backend/kubernetes.rs @@ -190,6 +190,8 @@ impl MetadataBackend for KubernetesBackend { identity: &SourceIdentity, worker_id: &str, worker: WorkerMetadata, + pod_name: &str, + pod_uid: &str, ) -> MetadataResult<()> { let source_id = crate::p2p::source_identity::compute_mx_source_id(identity); let source_id = source_id.as_str(); @@ -202,6 +204,7 @@ impl MetadataBackend for KubernetesBackend { // First, ensure the CR exists let existing = api.get_opt(&cr_name).await?; + let has_pod_name_and_uid = !pod_name.is_empty() && !pod_uid.is_empty(); if existing.is_none() { let new_cr = ModelMetadata { @@ -220,6 +223,20 @@ impl MetadataBackend for KubernetesBackend { ); labels }), + owner_references: if has_pod_name_and_uid { + Some(vec![ + k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference { + api_version: "v1".to_string(), + kind: "Pod".to_string(), + name: pod_name.to_string(), + uid: pod_uid.to_string(), + controller: Some(false), + block_owner_deletion: Some(false), + }, + ]) + } else { + None + }, ..Default::default() }, spec: ModelMetadataSpec { diff --git a/modelexpress_server/src/p2p/backend/memory.rs b/modelexpress_server/src/p2p/backend/memory.rs index 50b6983f..a32f9370 100644 --- a/modelexpress_server/src/p2p/backend/memory.rs +++ b/modelexpress_server/src/p2p/backend/memory.rs @@ -59,6 +59,8 @@ impl MetadataBackend for InMemoryMetadataBackend { identity: &SourceIdentity, worker_id: &str, worker: WorkerMetadata, + _pod_name: &str, + _pod_uid: &str, ) -> MetadataResult<()> { let source_id = crate::p2p::source_identity::compute_mx_source_id(identity); let record = WorkerRecord::from(worker); @@ -233,7 +235,7 @@ mod tests { let source_id = compute_mx_source_id(&id); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Ready)) + .publish_metadata(&id, "w1", worker(0, SourceStatus::Ready), "", "") .await .expect("publish"); let record = backend @@ -267,7 +269,7 @@ mod tests { for rank in [2, 0, 1] { backend - .publish_metadata(&id, "w1", worker(rank, SourceStatus::Ready)) + .publish_metadata(&id, "w1", worker(rank, SourceStatus::Ready), "", "") .await .expect("publish"); } @@ -288,11 +290,11 @@ mod tests { let id = identity("m"); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing)) + .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing), "", "") .await .expect("publish r0"); backend - .publish_metadata(&id, "w1", worker(1, SourceStatus::Ready)) + .publish_metadata(&id, "w1", worker(1, SourceStatus::Ready), "", "") .await .expect("publish r1"); @@ -332,11 +334,11 @@ mod tests { let source_id = compute_mx_source_id(&id); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing)) + .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing), "", "") .await .expect("publish r0"); backend - .publish_metadata(&id, "w1", worker(3, SourceStatus::Ready)) + .publish_metadata(&id, "w1", worker(3, SourceStatus::Ready), "", "") .await .expect("publish r3"); @@ -361,7 +363,7 @@ mod tests { let source_id = compute_mx_source_id(&id); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing)) + .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing), "", "") .await .expect("publish"); backend @@ -401,11 +403,11 @@ mod tests { let source_id = compute_mx_source_id(&id); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Ready)) + .publish_metadata(&id, "w1", worker(0, SourceStatus::Ready), "", "") .await .expect("publish w1"); backend - .publish_metadata(&id, "w2", worker(0, SourceStatus::Ready)) + .publish_metadata(&id, "w2", worker(0, SourceStatus::Ready), "", "") .await .expect("publish w2"); diff --git a/modelexpress_server/src/p2p/backend/redis.rs b/modelexpress_server/src/p2p/backend/redis.rs index edbd20af..c9aee250 100644 --- a/modelexpress_server/src/p2p/backend/redis.rs +++ b/modelexpress_server/src/p2p/backend/redis.rs @@ -407,6 +407,8 @@ impl MetadataBackend for RedisBackend { identity: &SourceIdentity, worker_id: &str, worker: WorkerMetadata, + _pod_name: &str, + _pod_uid: &str, ) -> MetadataResult<()> { let source_id = crate::p2p::source_identity::compute_mx_source_id(identity); let mut conn = self.get_conn().await?; diff --git a/modelexpress_server/src/p2p/service.rs b/modelexpress_server/src/p2p/service.rs index 523b8d3e..32d4eb24 100644 --- a/modelexpress_server/src/p2p/service.rs +++ b/modelexpress_server/src/p2p/service.rs @@ -116,7 +116,7 @@ impl P2pService for P2pServiceImpl { match self .state - .publish_metadata(&identity, &worker_id, worker) + .publish_metadata(&identity, &worker_id, worker, &req.pod_name, &req.pod_uid) .await { Ok(()) => { @@ -385,6 +385,7 @@ mod tests { identity: None, worker: None, worker_id: "worker-uuid-1".to_string(), + ..Default::default() })) .await .expect("rpc") @@ -403,6 +404,7 @@ mod tests { identity: Some(id), worker: None, worker_id: "worker-uuid-1".to_string(), + ..Default::default() })) .await .expect("rpc") @@ -418,6 +420,7 @@ mod tests { identity: Some(test_identity()), worker: None, worker_id: String::new(), + ..Default::default() })) .await .expect("rpc") @@ -431,7 +434,7 @@ mod tests { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() .once() - .returning(|_, _, _| Ok(())); + .returning(|_, _, _, _, _| Ok(())); let svc = make_service(mock); let resp = svc @@ -448,6 +451,7 @@ mod tests { ..Default::default() }), worker_id: "worker-uuid-1".to_string(), + ..Default::default() })) .await .expect("rpc") @@ -463,7 +467,7 @@ mod tests { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() .once() - .returning(|_, _, _| Err("storage unavailable".into())); + .returning(|_, _, _, _, _| Err("storage unavailable".into())); let svc = make_service(mock); let resp = svc @@ -478,6 +482,7 @@ mod tests { ..Default::default() }), worker_id: "worker-uuid-1".to_string(), + ..Default::default() })) .await .expect("rpc") @@ -649,6 +654,7 @@ mod tests { identity: Some(test_identity()), worker: None, worker_id: "worker-uuid-1".to_string(), + ..Default::default() })) .await .expect("rpc") diff --git a/modelexpress_server/src/p2p/state.rs b/modelexpress_server/src/p2p/state.rs index 4a42b183..5fddc950 100644 --- a/modelexpress_server/src/p2p/state.rs +++ b/modelexpress_server/src/p2p/state.rs @@ -95,10 +95,12 @@ impl P2pStateManager { identity: &SourceIdentity, worker_id: &str, worker: WorkerMetadata, + pod_name: &str, + pod_uid: &str, ) -> MetadataResult<()> { self.get_backend() .await? - .publish_metadata(identity, worker_id, worker) + .publish_metadata(identity, worker_id, worker, pod_name, pod_uid) .await } @@ -457,14 +459,14 @@ mod tests { async fn test_publish_metadata_calls_backend() { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() - .withf(|identity, worker_id, worker| { + .withf(|identity, worker_id, worker, _, _| { identity.model_name == "my-model" && identity.tensor_parallel_size == 8 && worker_id == "a1b2c3d4" && worker.worker_rank == 3 }) .once() - .returning(|_, _, _| Ok(())); + .returning(|_, _, _, _, _| Ok(())); let manager = P2pStateManager::with_backend(Arc::new(mock)); manager @@ -478,6 +480,8 @@ mod tests { updated_at: 0, ..Default::default() }, + "", + "", ) .await .expect("publish_metadata failed"); @@ -488,12 +492,12 @@ mod tests { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() .once() - .returning(|_, _, _| Err("storage unavailable".into())); + .returning(|_, _, _, _, _| Err("storage unavailable".into())); let manager = P2pStateManager::with_backend(Arc::new(mock)); assert!( manager - .publish_metadata(&test_identity(), "a1b2c3d4", WorkerMetadata::default()) + .publish_metadata(&test_identity(), "a1b2c3d4", WorkerMetadata::default(), "", "") .await .is_err() ); From 4d96cc39656d69ce701e8c93ff2d1d3965bdb0df Mon Sep 17 00:00:00 2001 From: Omer Aplatony Date: Tue, 14 Jul 2026 13:41:29 +0300 Subject: [PATCH 2/6] update proto Signed-off-by: Omer Aplatony --- .../python/modelexpress/p2p_pb2.py | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/modelexpress_client/python/modelexpress/p2p_pb2.py b/modelexpress_client/python/modelexpress/p2p_pb2.py index faaaec2c..654da8cc 100644 --- a/modelexpress_client/python/modelexpress/p2p_pb2.py +++ b/modelexpress_client/python/modelexpress/p2p_pb2.py @@ -27,7 +27,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tp2p.proto\x12\x11model_express.p2p\"\xe7\x04\n\x0eSourceIdentity\x12\x12\n\nmx_version\x18\x01 \x01(\t\x12\x37\n\x0emx_source_type\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\nmodel_name\x18\x03 \x01(\t\x12>\n\x11\x62\x61\x63kend_framework\x18\x04 \x01(\x0e\x32#.model_express.p2p.BackendFramework\x12\x1c\n\x14tensor_parallel_size\x18\x05 \x01(\r\x12\x1e\n\x16pipeline_parallel_size\x18\x06 \x01(\r\x12\x1c\n\x14\x65xpert_parallel_size\x18\x07 \x01(\r\x12\r\n\x05\x64type\x18\x08 \x01(\t\x12\x14\n\x0cquantization\x18\t \x01(\t\x12P\n\x10\x65xtra_parameters\x18\n \x03(\x0b\x32\x36.model_express.p2p.SourceIdentity.ExtraParametersEntry\x12\x10\n\x08revision\x18\x0b \x01(\t\x12!\n\x19\x62\x61\x63kend_framework_version\x18\x0c \x01(\t\x12\x15\n\rtorch_version\x18\r \x01(\t\x12\x14\n\x0c\x63uda_version\x18\x0e \x01(\t\x12\x16\n\x0etriton_version\x18\x0f \x01(\t\x12\x10\n\x08gpu_arch\x18\x10 \x01(\t\x12\x1d\n\x15\x63ompile_config_digest\x18\x11 \x01(\t\x1a\x36\n\x14\x45xtraParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"^\n\x10TensorDescriptor\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x61\x64\x64r\x18\x02 \x01(\x04\x12\x0c\n\x04size\x18\x03 \x01(\x04\x12\x11\n\tdevice_id\x18\x04 \x01(\r\x12\r\n\x05\x64type\x18\x05 \x01(\t\"}\n\x16\x41rtifactSourceMetadata\x12\x13\n\x0b\x61rtifact_id\x18\x01 \x01(\t\x12\x12\n\ntotal_size\x18\x02 \x01(\x04\x12\x12\n\nfile_count\x18\x03 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x04 \x01(\r\x12\x11\n\tnode_rank\x18\x05 \x01(\r\"L\n\x14TensorSourceMetadata\x12\x34\n\x07tensors\x18\x01 \x03(\x0b\x32#.model_express.p2p.TensorDescriptor\"\xeb\x01\n\x10\x41rtifactManifest\x12\x18\n\x10manifest_version\x18\x01 \x01(\r\x12\x37\n\x0emx_source_type\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\nchunk_size\x18\x03 \x01(\x04\x12\x36\n\x05\x66iles\x18\x04 \x03(\x0b\x32\'.model_express.p2p.ArtifactManifestFile\x12\x38\n\x06\x63hunks\x18\x05 \x03(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\"X\n\x14\x41rtifactManifestFile\x12\x12\n\nfile_index\x18\x01 \x01(\r\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\x0c\n\x04size\x18\x03 \x01(\x04\x12\x10\n\x08\x63hecksum\x18\x04 \x01(\t\"w\n\x15\x41rtifactManifestChunk\x12\x13\n\x0b\x63hunk_index\x18\x01 \x01(\r\x12\x12\n\nfile_index\x18\x02 \x01(\r\x12\x13\n\x0b\x66ile_offset\x18\x03 \x01(\x04\x12\x0e\n\x06length\x18\x04 \x01(\x04\x12\x10\n\x08\x63hecksum\x18\x05 \x01(\t\"\xf3\x03\n\x0eWorkerMetadata\x12\x13\n\x0bworker_rank\x18\x01 \x01(\r\x12\x17\n\rnixl_metadata\x18\x02 \x01(\x0cH\x00\x12$\n\x1atransfer_engine_session_id\x18\n \x01(\tH\x00\x12\x38\n\x07tensors\x18\x03 \x03(\x0b\x32#.model_express.p2p.TensorDescriptorB\x02\x18\x01\x12/\n\x06status\x18\x04 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatus\x12\x12\n\nupdated_at\x18\x05 \x01(\x03\x12\x19\n\x11metadata_endpoint\x18\x06 \x01(\t\x12\x12\n\nagent_name\x18\x07 \x01(\t\x12\x1c\n\x14worker_grpc_endpoint\x18\x08 \x01(\t\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\t \x01(\t\x12@\n\rtensor_source\x18\x14 \x01(\x0b\x32\'.model_express.p2p.TensorSourceMetadataH\x01\x12\x44\n\x0f\x61rtifact_source\x18\x15 \x01(\x0b\x32).model_express.p2p.ArtifactSourceMetadataH\x01\x42\x12\n\x10\x62\x61\x63kend_metadataB\x10\n\x0esource_payload\"V\n\x18GetTensorManifestRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x16\n\tworker_id\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_worker_id\"\xe6\x01\n\x19GetTensorManifestResponse\x12\x34\n\x07tensors\x18\x01 \x03(\x0b\x32#.model_express.p2p.TensorDescriptor\x12\x14\n\x0cmx_source_id\x18\x02 \x01(\t\x12\x19\n\x11metadata_endpoint\x18\x03 \x01(\t\x12\x12\n\nagent_name\x18\x04 \x01(\t\x12\x13\n\x0bworker_rank\x18\x05 \x01(\r\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\x06 \x01(\t\x12\x16\n\tworker_id\x18\x07 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_worker_id\"M\n GetArtifactManifestHeaderRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\"\xee\x02\n!GetArtifactManifestHeaderResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x18\n\x10manifest_version\x18\x03 \x01(\r\x12\x37\n\x0emx_source_type\x18\x04 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\ntotal_size\x18\x05 \x01(\x04\x12\x12\n\nfile_count\x18\x06 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x07 \x01(\r\x12\x12\n\nchunk_size\x18\x08 \x01(\x04\x12\x19\n\x11metadata_endpoint\x18\t \x01(\t\x12\x12\n\nagent_name\x18\n \x01(\t\x12\x13\n\x0bworker_rank\x18\x0b \x01(\r\x12\x36\n\x05\x66iles\x18\x0c \x03(\x0b\x32\'.model_express.p2p.ArtifactManifestFile\"|\n GetArtifactManifestChunksRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x19\n\x11start_chunk_index\x18\x03 \x01(\r\x12\x12\n\nmax_chunks\x18\x04 \x01(\r\"\xbc\x01\n!GetArtifactManifestChunksResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x19\n\x11start_chunk_index\x18\x03 \x01(\r\x12\x38\n\x06\x63hunks\x18\x04 \x03(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\x12\x17\n\x0fnext_page_token\x18\x05 \x01(\t\"R\n\x1f\x41rtifactChunkTransferDescriptor\x12\x0c\n\x04\x61\x64\x64r\x18\x01 \x01(\x04\x12\x0e\n\x06length\x18\x02 \x01(\x04\x12\x11\n\tdevice_id\x18\x03 \x01(\r\"]\n\x1bPrepareArtifactChunkRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x13\n\x0b\x63hunk_index\x18\x03 \x01(\r\"\xf1\x01\n\x1cPrepareArtifactChunkResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x10\n\x08lease_id\x18\x03 \x01(\t\x12\x37\n\x05\x63hunk\x18\x04 \x01(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\x12\x42\n\x06source\x18\x05 \x01(\x0b\x32\x32.model_express.p2p.ArtifactChunkTransferDescriptor\x12\x17\n\x0fsource_metadata\x18\x06 \x01(\x0c\"Z\n\x1bReleaseArtifactChunkRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x10\n\x08lease_id\x18\x03 \x01(\t\"\x82\x01\n\x1cReleaseArtifactChunkResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x37\n\x05\x63hunk\x18\x03 \x01(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\"\x93\x01\n\x16PublishMetadataRequest\x12\x33\n\x08identity\x18\x01 \x01(\x0b\x32!.model_express.p2p.SourceIdentity\x12\x31\n\x06worker\x18\x02 \x01(\x0b\x32!.model_express.p2p.WorkerMetadata\x12\x11\n\tworker_id\x18\x03 \x01(\t\"d\n\x17PublishMetadataResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x14\n\x0cmx_source_id\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\"z\n\x11SourceInstanceRef\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x11\n\tworker_id\x18\x02 \x01(\t\x12\x12\n\nmodel_name\x18\x03 \x01(\t\x12\x13\n\x0bworker_rank\x18\x04 \x01(\r\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\x05 \x01(\t\"\x98\x01\n\x12ListSourcesRequest\x12\x33\n\x08identity\x18\x01 \x01(\x0b\x32!.model_express.p2p.SourceIdentity\x12;\n\rstatus_filter\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatusH\x00\x88\x01\x01\x42\x10\n\x0e_status_filter\"N\n\x13ListSourcesResponse\x12\x37\n\tinstances\x18\x01 \x03(\x0b\x32$.model_express.p2p.SourceInstanceRef\"=\n\x12GetMetadataRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x11\n\tworker_id\x18\x02 \x01(\t\"\x80\x01\n\x13GetMetadataResponse\x12\r\n\x05\x66ound\x18\x01 \x01(\x08\x12\x31\n\x06worker\x18\x02 \x01(\x0b\x32!.model_express.p2p.WorkerMetadata\x12\x14\n\x0cmx_source_id\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\"\x84\x01\n\x13UpdateStatusRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0bworker_rank\x18\x02 \x01(\r\x12/\n\x06status\x18\x03 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatus\x12\x11\n\tworker_id\x18\x04 \x01(\t\"8\n\x14UpdateStatusResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t*\x8a\x01\n\x10\x42\x61\x63kendFramework\x12\x1d\n\x19\x42\x41\x43KEND_FRAMEWORK_UNKNOWN\x10\x00\x12\x1a\n\x16\x42\x41\x43KEND_FRAMEWORK_VLLM\x10\x01\x12\x1c\n\x18\x42\x41\x43KEND_FRAMEWORK_SGLANG\x10\x02\x12\x1d\n\x19\x42\x41\x43KEND_FRAMEWORK_TRT_LLM\x10\x03*\xba\x02\n\x0cMxSourceType\x12\x1a\n\x16MX_SOURCE_TYPE_WEIGHTS\x10\x00\x12\x17\n\x13MX_SOURCE_TYPE_LORA\x10\x01\x12\x1d\n\x19MX_SOURCE_TYPE_CUDA_GRAPH\x10\x02\x12&\n\"MX_SOURCE_TYPE_TORCH_COMPILE_CACHE\x10\x03\x12\x1f\n\x1bMX_SOURCE_TYPE_TRITON_CACHE\x10\x04\x12\"\n\x1eMX_SOURCE_TYPE_DEEP_GEMM_CACHE\x10\x05\x12!\n\x1dMX_SOURCE_TYPE_TILELANG_CACHE\x10\x06\x12!\n\x1dMX_SOURCE_TYPE_CUTE_DSL_CACHE\x10\x07\x12#\n\x1fMX_SOURCE_TYPE_FLASHINFER_CACHE\x10\x08*{\n\x0cSourceStatus\x12\x19\n\x15SOURCE_STATUS_UNKNOWN\x10\x00\x12\x1e\n\x1aSOURCE_STATUS_INITIALIZING\x10\x01\x12\x17\n\x13SOURCE_STATUS_READY\x10\x02\x12\x17\n\x13SOURCE_STATUS_STALE\x10\x03\x32\x93\x03\n\nP2pService\x12h\n\x0fPublishMetadata\x12).model_express.p2p.PublishMetadataRequest\x1a*.model_express.p2p.PublishMetadataResponse\x12\\\n\x0bListSources\x12%.model_express.p2p.ListSourcesRequest\x1a&.model_express.p2p.ListSourcesResponse\x12\\\n\x0bGetMetadata\x12%.model_express.p2p.GetMetadataRequest\x1a&.model_express.p2p.GetMetadataResponse\x12_\n\x0cUpdateStatus\x12&.model_express.p2p.UpdateStatusRequest\x1a\'.model_express.p2p.UpdateStatusResponse2\x83\x05\n\rWorkerService\x12n\n\x11GetTensorManifest\x12+.model_express.p2p.GetTensorManifestRequest\x1a,.model_express.p2p.GetTensorManifestResponse\x12\x86\x01\n\x19GetArtifactManifestHeader\x12\x33.model_express.p2p.GetArtifactManifestHeaderRequest\x1a\x34.model_express.p2p.GetArtifactManifestHeaderResponse\x12\x86\x01\n\x19GetArtifactManifestChunks\x12\x33.model_express.p2p.GetArtifactManifestChunksRequest\x1a\x34.model_express.p2p.GetArtifactManifestChunksResponse\x12w\n\x14PrepareArtifactChunk\x12..model_express.p2p.PrepareArtifactChunkRequest\x1a/.model_express.p2p.PrepareArtifactChunkResponse\x12w\n\x14ReleaseArtifactChunk\x12..model_express.p2p.ReleaseArtifactChunkRequest\x1a/.model_express.p2p.ReleaseArtifactChunkResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tp2p.proto\x12\x11model_express.p2p\"\xe7\x04\n\x0eSourceIdentity\x12\x12\n\nmx_version\x18\x01 \x01(\t\x12\x37\n\x0emx_source_type\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\nmodel_name\x18\x03 \x01(\t\x12>\n\x11\x62\x61\x63kend_framework\x18\x04 \x01(\x0e\x32#.model_express.p2p.BackendFramework\x12\x1c\n\x14tensor_parallel_size\x18\x05 \x01(\r\x12\x1e\n\x16pipeline_parallel_size\x18\x06 \x01(\r\x12\x1c\n\x14\x65xpert_parallel_size\x18\x07 \x01(\r\x12\r\n\x05\x64type\x18\x08 \x01(\t\x12\x14\n\x0cquantization\x18\t \x01(\t\x12P\n\x10\x65xtra_parameters\x18\n \x03(\x0b\x32\x36.model_express.p2p.SourceIdentity.ExtraParametersEntry\x12\x10\n\x08revision\x18\x0b \x01(\t\x12!\n\x19\x62\x61\x63kend_framework_version\x18\x0c \x01(\t\x12\x15\n\rtorch_version\x18\r \x01(\t\x12\x14\n\x0c\x63uda_version\x18\x0e \x01(\t\x12\x16\n\x0etriton_version\x18\x0f \x01(\t\x12\x10\n\x08gpu_arch\x18\x10 \x01(\t\x12\x1d\n\x15\x63ompile_config_digest\x18\x11 \x01(\t\x1a\x36\n\x14\x45xtraParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"^\n\x10TensorDescriptor\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x61\x64\x64r\x18\x02 \x01(\x04\x12\x0c\n\x04size\x18\x03 \x01(\x04\x12\x11\n\tdevice_id\x18\x04 \x01(\r\x12\r\n\x05\x64type\x18\x05 \x01(\t\"}\n\x16\x41rtifactSourceMetadata\x12\x13\n\x0b\x61rtifact_id\x18\x01 \x01(\t\x12\x12\n\ntotal_size\x18\x02 \x01(\x04\x12\x12\n\nfile_count\x18\x03 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x04 \x01(\r\x12\x11\n\tnode_rank\x18\x05 \x01(\r\"L\n\x14TensorSourceMetadata\x12\x34\n\x07tensors\x18\x01 \x03(\x0b\x32#.model_express.p2p.TensorDescriptor\"\xeb\x01\n\x10\x41rtifactManifest\x12\x18\n\x10manifest_version\x18\x01 \x01(\r\x12\x37\n\x0emx_source_type\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\nchunk_size\x18\x03 \x01(\x04\x12\x36\n\x05\x66iles\x18\x04 \x03(\x0b\x32\'.model_express.p2p.ArtifactManifestFile\x12\x38\n\x06\x63hunks\x18\x05 \x03(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\"X\n\x14\x41rtifactManifestFile\x12\x12\n\nfile_index\x18\x01 \x01(\r\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\x0c\n\x04size\x18\x03 \x01(\x04\x12\x10\n\x08\x63hecksum\x18\x04 \x01(\t\"w\n\x15\x41rtifactManifestChunk\x12\x13\n\x0b\x63hunk_index\x18\x01 \x01(\r\x12\x12\n\nfile_index\x18\x02 \x01(\r\x12\x13\n\x0b\x66ile_offset\x18\x03 \x01(\x04\x12\x0e\n\x06length\x18\x04 \x01(\x04\x12\x10\n\x08\x63hecksum\x18\x05 \x01(\t\"\xf3\x03\n\x0eWorkerMetadata\x12\x13\n\x0bworker_rank\x18\x01 \x01(\r\x12\x17\n\rnixl_metadata\x18\x02 \x01(\x0cH\x00\x12$\n\x1atransfer_engine_session_id\x18\n \x01(\tH\x00\x12\x38\n\x07tensors\x18\x03 \x03(\x0b\x32#.model_express.p2p.TensorDescriptorB\x02\x18\x01\x12/\n\x06status\x18\x04 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatus\x12\x12\n\nupdated_at\x18\x05 \x01(\x03\x12\x19\n\x11metadata_endpoint\x18\x06 \x01(\t\x12\x12\n\nagent_name\x18\x07 \x01(\t\x12\x1c\n\x14worker_grpc_endpoint\x18\x08 \x01(\t\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\t \x01(\t\x12@\n\rtensor_source\x18\x14 \x01(\x0b\x32\'.model_express.p2p.TensorSourceMetadataH\x01\x12\x44\n\x0f\x61rtifact_source\x18\x15 \x01(\x0b\x32).model_express.p2p.ArtifactSourceMetadataH\x01\x42\x12\n\x10\x62\x61\x63kend_metadataB\x10\n\x0esource_payload\"V\n\x18GetTensorManifestRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x16\n\tworker_id\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_worker_id\"\xe6\x01\n\x19GetTensorManifestResponse\x12\x34\n\x07tensors\x18\x01 \x03(\x0b\x32#.model_express.p2p.TensorDescriptor\x12\x14\n\x0cmx_source_id\x18\x02 \x01(\t\x12\x19\n\x11metadata_endpoint\x18\x03 \x01(\t\x12\x12\n\nagent_name\x18\x04 \x01(\t\x12\x13\n\x0bworker_rank\x18\x05 \x01(\r\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\x06 \x01(\t\x12\x16\n\tworker_id\x18\x07 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_worker_id\"M\n GetArtifactManifestHeaderRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\"\xee\x02\n!GetArtifactManifestHeaderResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x18\n\x10manifest_version\x18\x03 \x01(\r\x12\x37\n\x0emx_source_type\x18\x04 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\ntotal_size\x18\x05 \x01(\x04\x12\x12\n\nfile_count\x18\x06 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x07 \x01(\r\x12\x12\n\nchunk_size\x18\x08 \x01(\x04\x12\x19\n\x11metadata_endpoint\x18\t \x01(\t\x12\x12\n\nagent_name\x18\n \x01(\t\x12\x13\n\x0bworker_rank\x18\x0b \x01(\r\x12\x36\n\x05\x66iles\x18\x0c \x03(\x0b\x32\'.model_express.p2p.ArtifactManifestFile\"|\n GetArtifactManifestChunksRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x19\n\x11start_chunk_index\x18\x03 \x01(\r\x12\x12\n\nmax_chunks\x18\x04 \x01(\r\"\xbc\x01\n!GetArtifactManifestChunksResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x19\n\x11start_chunk_index\x18\x03 \x01(\r\x12\x38\n\x06\x63hunks\x18\x04 \x03(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\x12\x17\n\x0fnext_page_token\x18\x05 \x01(\t\"R\n\x1f\x41rtifactChunkTransferDescriptor\x12\x0c\n\x04\x61\x64\x64r\x18\x01 \x01(\x04\x12\x0e\n\x06length\x18\x02 \x01(\x04\x12\x11\n\tdevice_id\x18\x03 \x01(\r\"]\n\x1bPrepareArtifactChunkRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x13\n\x0b\x63hunk_index\x18\x03 \x01(\r\"\xf1\x01\n\x1cPrepareArtifactChunkResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x10\n\x08lease_id\x18\x03 \x01(\t\x12\x37\n\x05\x63hunk\x18\x04 \x01(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\x12\x42\n\x06source\x18\x05 \x01(\x0b\x32\x32.model_express.p2p.ArtifactChunkTransferDescriptor\x12\x17\n\x0fsource_metadata\x18\x06 \x01(\x0c\"Z\n\x1bReleaseArtifactChunkRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x10\n\x08lease_id\x18\x03 \x01(\t\"\x82\x01\n\x1cReleaseArtifactChunkResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x37\n\x05\x63hunk\x18\x03 \x01(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\"\xb6\x01\n\x16PublishMetadataRequest\x12\x33\n\x08identity\x18\x01 \x01(\x0b\x32!.model_express.p2p.SourceIdentity\x12\x31\n\x06worker\x18\x02 \x01(\x0b\x32!.model_express.p2p.WorkerMetadata\x12\x11\n\tworker_id\x18\x03 \x01(\t\x12\x10\n\x08pod_name\x18\x04 \x01(\t\x12\x0f\n\x07pod_uid\x18\x05 \x01(\t\"d\n\x17PublishMetadataResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x14\n\x0cmx_source_id\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\"z\n\x11SourceInstanceRef\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x11\n\tworker_id\x18\x02 \x01(\t\x12\x12\n\nmodel_name\x18\x03 \x01(\t\x12\x13\n\x0bworker_rank\x18\x04 \x01(\r\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\x05 \x01(\t\"\x98\x01\n\x12ListSourcesRequest\x12\x33\n\x08identity\x18\x01 \x01(\x0b\x32!.model_express.p2p.SourceIdentity\x12;\n\rstatus_filter\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatusH\x00\x88\x01\x01\x42\x10\n\x0e_status_filter\"N\n\x13ListSourcesResponse\x12\x37\n\tinstances\x18\x01 \x03(\x0b\x32$.model_express.p2p.SourceInstanceRef\"=\n\x12GetMetadataRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x11\n\tworker_id\x18\x02 \x01(\t\"\x80\x01\n\x13GetMetadataResponse\x12\r\n\x05\x66ound\x18\x01 \x01(\x08\x12\x31\n\x06worker\x18\x02 \x01(\x0b\x32!.model_express.p2p.WorkerMetadata\x12\x14\n\x0cmx_source_id\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\"\x84\x01\n\x13UpdateStatusRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0bworker_rank\x18\x02 \x01(\r\x12/\n\x06status\x18\x03 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatus\x12\x11\n\tworker_id\x18\x04 \x01(\t\"8\n\x14UpdateStatusResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t*\x8a\x01\n\x10\x42\x61\x63kendFramework\x12\x1d\n\x19\x42\x41\x43KEND_FRAMEWORK_UNKNOWN\x10\x00\x12\x1a\n\x16\x42\x41\x43KEND_FRAMEWORK_VLLM\x10\x01\x12\x1c\n\x18\x42\x41\x43KEND_FRAMEWORK_SGLANG\x10\x02\x12\x1d\n\x19\x42\x41\x43KEND_FRAMEWORK_TRT_LLM\x10\x03*\xba\x02\n\x0cMxSourceType\x12\x1a\n\x16MX_SOURCE_TYPE_WEIGHTS\x10\x00\x12\x17\n\x13MX_SOURCE_TYPE_LORA\x10\x01\x12\x1d\n\x19MX_SOURCE_TYPE_CUDA_GRAPH\x10\x02\x12&\n\"MX_SOURCE_TYPE_TORCH_COMPILE_CACHE\x10\x03\x12\x1f\n\x1bMX_SOURCE_TYPE_TRITON_CACHE\x10\x04\x12\"\n\x1eMX_SOURCE_TYPE_DEEP_GEMM_CACHE\x10\x05\x12!\n\x1dMX_SOURCE_TYPE_TILELANG_CACHE\x10\x06\x12!\n\x1dMX_SOURCE_TYPE_CUTE_DSL_CACHE\x10\x07\x12#\n\x1fMX_SOURCE_TYPE_FLASHINFER_CACHE\x10\x08*{\n\x0cSourceStatus\x12\x19\n\x15SOURCE_STATUS_UNKNOWN\x10\x00\x12\x1e\n\x1aSOURCE_STATUS_INITIALIZING\x10\x01\x12\x17\n\x13SOURCE_STATUS_READY\x10\x02\x12\x17\n\x13SOURCE_STATUS_STALE\x10\x03\x32\x93\x03\n\nP2pService\x12h\n\x0fPublishMetadata\x12).model_express.p2p.PublishMetadataRequest\x1a*.model_express.p2p.PublishMetadataResponse\x12\\\n\x0bListSources\x12%.model_express.p2p.ListSourcesRequest\x1a&.model_express.p2p.ListSourcesResponse\x12\\\n\x0bGetMetadata\x12%.model_express.p2p.GetMetadataRequest\x1a&.model_express.p2p.GetMetadataResponse\x12_\n\x0cUpdateStatus\x12&.model_express.p2p.UpdateStatusRequest\x1a\'.model_express.p2p.UpdateStatusResponse2\x83\x05\n\rWorkerService\x12n\n\x11GetTensorManifest\x12+.model_express.p2p.GetTensorManifestRequest\x1a,.model_express.p2p.GetTensorManifestResponse\x12\x86\x01\n\x19GetArtifactManifestHeader\x12\x33.model_express.p2p.GetArtifactManifestHeaderRequest\x1a\x34.model_express.p2p.GetArtifactManifestHeaderResponse\x12\x86\x01\n\x19GetArtifactManifestChunks\x12\x33.model_express.p2p.GetArtifactManifestChunksRequest\x1a\x34.model_express.p2p.GetArtifactManifestChunksResponse\x12w\n\x14PrepareArtifactChunk\x12..model_express.p2p.PrepareArtifactChunkRequest\x1a/.model_express.p2p.PrepareArtifactChunkResponse\x12w\n\x14ReleaseArtifactChunk\x12..model_express.p2p.ReleaseArtifactChunkRequest\x1a/.model_express.p2p.ReleaseArtifactChunkResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -38,12 +38,12 @@ _globals['_SOURCEIDENTITY_EXTRAPARAMETERSENTRY']._serialized_options = b'8\001' _globals['_WORKERMETADATA'].fields_by_name['tensors']._loaded_options = None _globals['_WORKERMETADATA'].fields_by_name['tensors']._serialized_options = b'\030\001' - _globals['_BACKENDFRAMEWORK']._serialized_start=4635 - _globals['_BACKENDFRAMEWORK']._serialized_end=4773 - _globals['_MXSOURCETYPE']._serialized_start=4776 - _globals['_MXSOURCETYPE']._serialized_end=5090 - _globals['_SOURCESTATUS']._serialized_start=5092 - _globals['_SOURCESTATUS']._serialized_end=5215 + _globals['_BACKENDFRAMEWORK']._serialized_start=4670 + _globals['_BACKENDFRAMEWORK']._serialized_end=4808 + _globals['_MXSOURCETYPE']._serialized_start=4811 + _globals['_MXSOURCETYPE']._serialized_end=5125 + _globals['_SOURCESTATUS']._serialized_start=5127 + _globals['_SOURCESTATUS']._serialized_end=5250 _globals['_SOURCEIDENTITY']._serialized_start=33 _globals['_SOURCEIDENTITY']._serialized_end=648 _globals['_SOURCEIDENTITY_EXTRAPARAMETERSENTRY']._serialized_start=594 @@ -85,25 +85,25 @@ _globals['_RELEASEARTIFACTCHUNKRESPONSE']._serialized_start=3504 _globals['_RELEASEARTIFACTCHUNKRESPONSE']._serialized_end=3634 _globals['_PUBLISHMETADATAREQUEST']._serialized_start=3637 - _globals['_PUBLISHMETADATAREQUEST']._serialized_end=3784 - _globals['_PUBLISHMETADATARESPONSE']._serialized_start=3786 - _globals['_PUBLISHMETADATARESPONSE']._serialized_end=3886 - _globals['_SOURCEINSTANCEREF']._serialized_start=3888 - _globals['_SOURCEINSTANCEREF']._serialized_end=4010 - _globals['_LISTSOURCESREQUEST']._serialized_start=4013 - _globals['_LISTSOURCESREQUEST']._serialized_end=4165 - _globals['_LISTSOURCESRESPONSE']._serialized_start=4167 - _globals['_LISTSOURCESRESPONSE']._serialized_end=4245 - _globals['_GETMETADATAREQUEST']._serialized_start=4247 - _globals['_GETMETADATAREQUEST']._serialized_end=4308 - _globals['_GETMETADATARESPONSE']._serialized_start=4311 - _globals['_GETMETADATARESPONSE']._serialized_end=4439 - _globals['_UPDATESTATUSREQUEST']._serialized_start=4442 - _globals['_UPDATESTATUSREQUEST']._serialized_end=4574 - _globals['_UPDATESTATUSRESPONSE']._serialized_start=4576 - _globals['_UPDATESTATUSRESPONSE']._serialized_end=4632 - _globals['_P2PSERVICE']._serialized_start=5218 - _globals['_P2PSERVICE']._serialized_end=5621 - _globals['_WORKERSERVICE']._serialized_start=5624 - _globals['_WORKERSERVICE']._serialized_end=6267 + _globals['_PUBLISHMETADATAREQUEST']._serialized_end=3819 + _globals['_PUBLISHMETADATARESPONSE']._serialized_start=3821 + _globals['_PUBLISHMETADATARESPONSE']._serialized_end=3921 + _globals['_SOURCEINSTANCEREF']._serialized_start=3923 + _globals['_SOURCEINSTANCEREF']._serialized_end=4045 + _globals['_LISTSOURCESREQUEST']._serialized_start=4048 + _globals['_LISTSOURCESREQUEST']._serialized_end=4200 + _globals['_LISTSOURCESRESPONSE']._serialized_start=4202 + _globals['_LISTSOURCESRESPONSE']._serialized_end=4280 + _globals['_GETMETADATAREQUEST']._serialized_start=4282 + _globals['_GETMETADATAREQUEST']._serialized_end=4343 + _globals['_GETMETADATARESPONSE']._serialized_start=4346 + _globals['_GETMETADATARESPONSE']._serialized_end=4474 + _globals['_UPDATESTATUSREQUEST']._serialized_start=4477 + _globals['_UPDATESTATUSREQUEST']._serialized_end=4609 + _globals['_UPDATESTATUSRESPONSE']._serialized_start=4611 + _globals['_UPDATESTATUSRESPONSE']._serialized_end=4667 + _globals['_P2PSERVICE']._serialized_start=5253 + _globals['_P2PSERVICE']._serialized_end=5656 + _globals['_WORKERSERVICE']._serialized_start=5659 + _globals['_WORKERSERVICE']._serialized_end=6302 # @@protoc_insertion_point(module_scope) From 95a21c64bc534e6b95e8511e0d9043c6023f445a Mon Sep 17 00:00:00 2001 From: Omer Aplatony Date: Tue, 14 Jul 2026 23:13:34 +0300 Subject: [PATCH 3/6] fixed ut Signed-off-by: Omer Aplatony --- modelexpress_client/python/tests/test_artifact_transfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modelexpress_client/python/tests/test_artifact_transfer.py b/modelexpress_client/python/tests/test_artifact_transfer.py index 8400b582..1cd15236 100644 --- a/modelexpress_client/python/tests/test_artifact_transfer.py +++ b/modelexpress_client/python/tests/test_artifact_transfer.py @@ -1368,7 +1368,7 @@ def __init__(self, mx_source_id: str | None = None): self.published_worker_id = "" self.status_updates = [] - def publish_metadata(self, identity, worker, worker_id): + def publish_metadata(self, identity, worker, worker_id, pod_name="", pod_uid=""): self.published_identity = p2p_pb2.SourceIdentity() self.published_identity.CopyFrom(identity) self.published_worker = p2p_pb2.WorkerMetadata() From 7755c00aa3fa2dd53530e69b22633ada5d1b2edc Mon Sep 17 00:00:00 2001 From: Omer Aplatony Date: Tue, 14 Jul 2026 23:17:54 +0300 Subject: [PATCH 4/6] fmt Signed-off-by: Omer Aplatony --- modelexpress_client/python/modelexpress/envs.py | 4 ++-- modelexpress_server/src/p2p/state.rs | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/modelexpress_client/python/modelexpress/envs.py b/modelexpress_client/python/modelexpress/envs.py index 8e3f242c..001d4bb3 100644 --- a/modelexpress_client/python/modelexpress/envs.py +++ b/modelexpress_client/python/modelexpress/envs.py @@ -215,8 +215,8 @@ def _env_float(name: str, default: float) -> float: "VLLM_ATTENTION_BACKEND": lambda: os.environ.get("VLLM_ATTENTION_BACKEND", "auto"), "HOSTNAME": lambda: os.environ.get("HOSTNAME", ""), "POD_NAMESPACE": lambda: os.environ.get("POD_NAMESPACE", ""), - "POD_NAME": lambda: os.environ.get("POD_NAME",""), - "POD_UID": lambda: os.environ.get("POD_UID","") + "POD_NAME": lambda: os.environ.get("POD_NAME", ""), + "POD_UID": lambda: os.environ.get("POD_UID", "") } diff --git a/modelexpress_server/src/p2p/state.rs b/modelexpress_server/src/p2p/state.rs index 5fddc950..9b186280 100644 --- a/modelexpress_server/src/p2p/state.rs +++ b/modelexpress_server/src/p2p/state.rs @@ -497,7 +497,13 @@ mod tests { let manager = P2pStateManager::with_backend(Arc::new(mock)); assert!( manager - .publish_metadata(&test_identity(), "a1b2c3d4", WorkerMetadata::default(), "", "") + .publish_metadata( + &test_identity(), + "a1b2c3d4", + WorkerMetadata::default(), + "", + "" + ) .await .is_err() ); From ebdb78ab3485f52786c7600c3c0d470168ce7305 Mon Sep 17 00:00:00 2001 From: Zheng Luo Date: Wed, 15 Jul 2026 10:39:35 -0700 Subject: [PATCH 5/6] fix: update pod ownership metadata Signed-off-by: Zheng Luo --- .../go/gen/modelexpress/p2p/p2p.pb.go | 29 +++++++- .../python/tests/test_artifact_transfer.py | 4 ++ .../src/p2p/backend/kubernetes.rs | 67 ++++++++++++++----- modelexpress_server/src/p2p/service.rs | 6 +- modelexpress_server/src/p2p/state.rs | 8 ++- 5 files changed, 91 insertions(+), 23 deletions(-) diff --git a/modelexpress_client/go/gen/modelexpress/p2p/p2p.pb.go b/modelexpress_client/go/gen/modelexpress/p2p/p2p.pb.go index 116d89cf..4d71e109 100644 --- a/modelexpress_client/go/gen/modelexpress/p2p/p2p.pb.go +++ b/modelexpress_client/go/gen/modelexpress/p2p/p2p.pb.go @@ -1937,7 +1937,14 @@ type PublishMetadataRequest struct { // Unique identifier for this running instance (UUID). // Differentiates multiple replicas with identical SourceIdentity. // If empty, the server rejects the request. - WorkerId string `protobuf:"bytes,3,opt,name=worker_id,json=workerId,proto3" json:"worker_id,omitempty"` + WorkerId string `protobuf:"bytes,3,opt,name=worker_id,json=workerId,proto3" json:"worker_id,omitempty"` + // Kubernetes pod name of the calling worker, set via the downward API. + // Used with pod_uid to set an ownerReference on the ModelMetadata CR + // so K8s garbage-collects it when the pod is deleted. + // When empty, no ownerReference is set. + PodName string `protobuf:"bytes,4,opt,name=pod_name,json=podName,proto3" json:"pod_name,omitempty"` + // Kubernetes pod UID of the calling worker, set via the downward API. + PodUid string `protobuf:"bytes,5,opt,name=pod_uid,json=podUid,proto3" json:"pod_uid,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1993,6 +2000,20 @@ func (x *PublishMetadataRequest) GetWorkerId() string { return "" } +func (x *PublishMetadataRequest) GetPodName() string { + if x != nil { + return x.PodName + } + return "" +} + +func (x *PublishMetadataRequest) GetPodUid() string { + if x != nil { + return x.PodUid + } + return "" +} + type PublishMetadataResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` @@ -2680,11 +2701,13 @@ const file_p2p_proto_rawDesc = "" + "mxSourceId\x12\x1f\n" + "\vartifact_id\x18\x02 \x01(\tR\n" + "artifactId\x12>\n" + - "\x05chunk\x18\x03 \x01(\v2(.model_express.p2p.ArtifactManifestChunkR\x05chunk\"\xaf\x01\n" + + "\x05chunk\x18\x03 \x01(\v2(.model_express.p2p.ArtifactManifestChunkR\x05chunk\"\xe3\x01\n" + "\x16PublishMetadataRequest\x12=\n" + "\bidentity\x18\x01 \x01(\v2!.model_express.p2p.SourceIdentityR\bidentity\x129\n" + "\x06worker\x18\x02 \x01(\v2!.model_express.p2p.WorkerMetadataR\x06worker\x12\x1b\n" + - "\tworker_id\x18\x03 \x01(\tR\bworkerId\"\x8c\x01\n" + + "\tworker_id\x18\x03 \x01(\tR\bworkerId\x12\x19\n" + + "\bpod_name\x18\x04 \x01(\tR\apodName\x12\x17\n" + + "\apod_uid\x18\x05 \x01(\tR\x06podUid\"\x8c\x01\n" + "\x17PublishMetadataResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12 \n" + diff --git a/modelexpress_client/python/tests/test_artifact_transfer.py b/modelexpress_client/python/tests/test_artifact_transfer.py index 1cd15236..0742143a 100644 --- a/modelexpress_client/python/tests/test_artifact_transfer.py +++ b/modelexpress_client/python/tests/test_artifact_transfer.py @@ -1366,6 +1366,8 @@ def __init__(self, mx_source_id: str | None = None): self.published_identity = None self.published_worker = None self.published_worker_id = "" + self.published_pod_name = "" + self.published_pod_uid = "" self.status_updates = [] def publish_metadata(self, identity, worker, worker_id, pod_name="", pod_uid=""): @@ -1374,6 +1376,8 @@ def publish_metadata(self, identity, worker, worker_id, pod_name="", pod_uid="") self.published_worker = p2p_pb2.WorkerMetadata() self.published_worker.CopyFrom(worker) self.published_worker_id = worker_id + self.published_pod_name = pod_name + self.published_pod_uid = pod_uid return self.mx_source_id or compute_mx_source_id(identity) def list_sources(self, identity=None, status_filter=None): diff --git a/modelexpress_server/src/p2p/backend/kubernetes.rs b/modelexpress_server/src/p2p/backend/kubernetes.rs index 340cb6e0..2cbb7b0c 100644 --- a/modelexpress_server/src/p2p/backend/kubernetes.rs +++ b/modelexpress_server/src/p2p/backend/kubernetes.rs @@ -14,7 +14,7 @@ use crate::p2p::k8s_types::{ }; use async_trait::async_trait; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; -use k8s_openapi::api::core::v1::ConfigMap; +use k8s_openapi::{api::core::v1::ConfigMap, apimachinery::pkg::apis::meta::v1::OwnerReference}; use kube::{ Client, api::{Api, ListParams, Patch, PatchParams, PostParams}, @@ -50,6 +50,21 @@ impl KubernetesBackend { Api::namespaced(self.client.clone(), &self.namespace) } + fn pod_owner_references(pod_name: &str, pod_uid: &str) -> Option> { + if pod_name.is_empty() || pod_uid.is_empty() { + return None; + } + + Some(vec![OwnerReference { + api_version: "v1".to_string(), + kind: "Pod".to_string(), + name: pod_name.to_string(), + uid: pod_uid.to_string(), + controller: Some(false), + block_owner_deletion: Some(false), + }]) + } + /// Create or update a ConfigMap with tensor descriptors for a worker. /// If `owner_uid` and `owner_name` are provided, sets ownerReferences /// so K8s garbage-collects ConfigMaps when the parent CR is deleted. @@ -204,7 +219,7 @@ impl MetadataBackend for KubernetesBackend { // First, ensure the CR exists let existing = api.get_opt(&cr_name).await?; - let has_pod_name_and_uid = !pod_name.is_empty() && !pod_uid.is_empty(); + let pod_owner_references = Self::pod_owner_references(pod_name, pod_uid); if existing.is_none() { let new_cr = ModelMetadata { @@ -223,20 +238,7 @@ impl MetadataBackend for KubernetesBackend { ); labels }), - owner_references: if has_pod_name_and_uid { - Some(vec![ - k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference { - api_version: "v1".to_string(), - kind: "Pod".to_string(), - name: pod_name.to_string(), - uid: pod_uid.to_string(), - controller: Some(false), - block_owner_deletion: Some(false), - }, - ]) - } else { - None - }, + owner_references: pod_owner_references.clone(), ..Default::default() }, spec: ModelMetadataSpec { @@ -264,6 +266,19 @@ impl MetadataBackend for KubernetesBackend { // Get CR UID for ownerReferences on ConfigMaps let cr = api.get(&cr_name).await?; + if let Some(owner_references) = &pod_owner_references + && cr.metadata.owner_references.as_ref() != Some(owner_references) + { + let owner_patch = json!({ + "metadata": { "ownerReferences": owner_references } + }); + api.patch( + &cr_name, + &PatchParams::default(), + &Patch::Merge(&owner_patch), + ) + .await?; + } let owner_uid = cr.metadata.uid.as_deref(); let owner_name = cr.metadata.name.as_deref(); @@ -828,4 +843,24 @@ mod tests { assert!(ArtifactSourceMetadataRecord::try_from(status).is_err()); } + + #[test] + fn pod_owner_references_require_name_and_uid() { + assert!(KubernetesBackend::pod_owner_references("pod-0", "").is_none()); + assert!(KubernetesBackend::pod_owner_references("", "pod-uid").is_none()); + } + + #[test] + fn pod_owner_references_identify_the_publishing_pod() { + let Some(owner_references) = KubernetesBackend::pod_owner_references("pod-0", "pod-uid") + else { + panic!("pod name and UID should produce an owner reference"); + }; + + assert_eq!(owner_references.len(), 1); + assert_eq!(owner_references[0].api_version, "v1"); + assert_eq!(owner_references[0].kind, "Pod"); + assert_eq!(owner_references[0].name, "pod-0"); + assert_eq!(owner_references[0].uid, "pod-uid"); + } } diff --git a/modelexpress_server/src/p2p/service.rs b/modelexpress_server/src/p2p/service.rs index 32d4eb24..f7c64e7b 100644 --- a/modelexpress_server/src/p2p/service.rs +++ b/modelexpress_server/src/p2p/service.rs @@ -433,6 +433,9 @@ mod tests { async fn test_publish_metadata_success() { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() + .withf(|_, _, _, pod_name, pod_uid| { + pod_name == "vllm-worker-0" && pod_uid == "pod-uid-1" + }) .once() .returning(|_, _, _, _, _| Ok(())); @@ -451,7 +454,8 @@ mod tests { ..Default::default() }), worker_id: "worker-uuid-1".to_string(), - ..Default::default() + pod_name: "vllm-worker-0".to_string(), + pod_uid: "pod-uid-1".to_string(), })) .await .expect("rpc") diff --git a/modelexpress_server/src/p2p/state.rs b/modelexpress_server/src/p2p/state.rs index 9b186280..a2880b9a 100644 --- a/modelexpress_server/src/p2p/state.rs +++ b/modelexpress_server/src/p2p/state.rs @@ -459,11 +459,13 @@ mod tests { async fn test_publish_metadata_calls_backend() { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() - .withf(|identity, worker_id, worker, _, _| { + .withf(|identity, worker_id, worker, pod_name, pod_uid| { identity.model_name == "my-model" && identity.tensor_parallel_size == 8 && worker_id == "a1b2c3d4" && worker.worker_rank == 3 + && pod_name == "vllm-worker-0" + && pod_uid == "pod-uid-1" }) .once() .returning(|_, _, _, _, _| Ok(())); @@ -480,8 +482,8 @@ mod tests { updated_at: 0, ..Default::default() }, - "", - "", + "vllm-worker-0", + "pod-uid-1", ) .await .expect("publish_metadata failed"); From 3a376cc9daf5a996b543023094b31063919f4a19 Mon Sep 17 00:00:00 2001 From: Zheng Luo Date: Wed, 15 Jul 2026 13:32:19 -0700 Subject: [PATCH 6/6] fix: make metadata pod ownership compatible Signed-off-by: Zheng Luo --- .../client/sgl/manifest-azure-mooncake.yaml | 12 ++++ ci/k8s/client/sgl/manifest-azure.yaml | 12 ++++ .../dynamo/manifest-azure-aggregated.yaml | 12 ++++ .../dynamo/manifest-azure-disaggregated.yaml | 24 ++++++++ ci/k8s/client/vllm/manifest-azure.yaml | 12 ++++ docs/DEPLOYMENT.md | 27 +++++++++ docs/metadata.md | 27 ++++++++- examples/p2p_transfer_k8s/client/README.md | 10 ++++ .../client/sglang/sglang-single-node-p2p.yaml | 12 ++++ .../sglang-single-node-transfer-engine.yaml | 12 ++++ .../client/vllm/vllm-multi-node.yaml | 8 +++ .../client/vllm/vllm-single-node.yaml | 8 +++ .../go/gen/modelexpress/p2p/p2p.pb.go | 23 ++++++-- .../python/modelexpress/client.py | 9 +-- .../metadata/k8s_service_client.py | 2 - .../python/modelexpress/metadata/publish.py | 14 +---- .../python/modelexpress/p2p_pb2.py | 56 +++++++++---------- .../python/tests/test_artifact_transfer.py | 6 +- .../python/tests/test_client.py | 33 +++++++++++ .../python/tests/test_types.py | 49 ++++++++++++++++ modelexpress_common/proto/p2p.proto | 10 +++- modelexpress_server/src/p2p/backend.rs | 1 + .../src/p2p/backend/kubernetes.rs | 32 ++++++++--- modelexpress_server/src/p2p/backend/memory.rs | 19 ++++--- modelexpress_server/src/p2p/backend/redis.rs | 1 + modelexpress_server/src/p2p/service.rs | 18 ++++-- modelexpress_server/src/p2p/state.rs | 37 ++++++++---- 27 files changed, 388 insertions(+), 98 deletions(-) create mode 100644 modelexpress_client/python/tests/test_client.py diff --git a/ci/k8s/client/sgl/manifest-azure-mooncake.yaml b/ci/k8s/client/sgl/manifest-azure-mooncake.yaml index e17f5e08..07daff2e 100644 --- a/ci/k8s/client/sgl/manifest-azure-mooncake.yaml +++ b/ci/k8s/client/sgl/manifest-azure-mooncake.yaml @@ -98,6 +98,18 @@ spec: - --modelexpress-config - '{"transport": "transfer_engine"}' env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: MX_SERVER_ADDRESS value: "mx-server:8000" - name: MX_RDMA_NIC_PIN diff --git a/ci/k8s/client/sgl/manifest-azure.yaml b/ci/k8s/client/sgl/manifest-azure.yaml index f2862abd..19c686fa 100644 --- a/ci/k8s/client/sgl/manifest-azure.yaml +++ b/ci/k8s/client/sgl/manifest-azure.yaml @@ -105,6 +105,18 @@ spec: - --modelexpress-config - '{"transport": "nixl"}' env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: MX_SERVER_ADDRESS value: "mx-server:8000" - name: MX_RDMA_NIC_PIN diff --git a/ci/k8s/client/vllm/dynamo/manifest-azure-aggregated.yaml b/ci/k8s/client/vllm/dynamo/manifest-azure-aggregated.yaml index 7a0581ef..57f19192 100644 --- a/ci/k8s/client/vllm/dynamo/manifest-azure-aggregated.yaml +++ b/ci/k8s/client/vllm/dynamo/manifest-azure-aggregated.yaml @@ -153,6 +153,18 @@ spec: custom: rdma/ib: "1" envs: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: VLLM_PLUGINS value: "modelexpress" - name: MX_SERVER_ADDRESS diff --git a/ci/k8s/client/vllm/dynamo/manifest-azure-disaggregated.yaml b/ci/k8s/client/vllm/dynamo/manifest-azure-disaggregated.yaml index 086617dc..348f9a7d 100644 --- a/ci/k8s/client/vllm/dynamo/manifest-azure-disaggregated.yaml +++ b/ci/k8s/client/vllm/dynamo/manifest-azure-disaggregated.yaml @@ -126,6 +126,18 @@ spec: custom: rdma/ib: "1" envs: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: VLLM_PLUGINS value: "modelexpress" - name: MX_SERVER_ADDRESS @@ -206,6 +218,18 @@ spec: custom: rdma/ib: "1" envs: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: VLLM_PLUGINS value: "modelexpress" - name: MX_SERVER_ADDRESS diff --git a/ci/k8s/client/vllm/manifest-azure.yaml b/ci/k8s/client/vllm/manifest-azure.yaml index ea1e990e..6bbb6c9a 100644 --- a/ci/k8s/client/vllm/manifest-azure.yaml +++ b/ci/k8s/client/vllm/manifest-azure.yaml @@ -107,6 +107,18 @@ spec: - --port - "${WORKER_PORT}" env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: VLLM_PLUGINS value: "modelexpress" - name: MX_SERVER_ADDRESS diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 8178f5dd..69e89701 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -105,6 +105,33 @@ reachable. To use the Kubernetes backend, apply `examples/crds.yaml` at cluster install time (installs both the `ModelMetadata` P2P CRD and the `ModelCacheEntry` registry CRD), then either enable `serviceAccount.rbac.enabled=true` on the Helm chart or apply `examples/p2p_transfer_k8s/server/kubernetes_backend/rbac-modelmetadata.yaml`. +For automatic cleanup of P2P metadata, expose the client Pod identity through +the Kubernetes Downward API. The checked-in vLLM, SGLang, and Dynamo manifests +already include these fields: + +```yaml +env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +``` + +With the Kubernetes metadata backend, a complete same-namespace identity makes +the Pod the owner of every weight or artifact `ModelMetadata` CR it publishes. +Kubernetes then garbage-collects those records when the Pod is deleted. Missing, +partial, or cross-namespace identity does not block publication; ModelExpress +omits the owner reference and retains the previous reaper-based lifecycle. Redis +and non-Kubernetes deployments ignore these fields. + #### Storage access modes MX has one configurable filesystem path, the model weights cache (`MODEL_EXPRESS_CACHE_DIRECTORY`, default `./cache`). Its access-mode requirement depends on deployment topology, not on MX itself: diff --git a/docs/metadata.md b/docs/metadata.md index bb6a3411..f28ef4bf 100644 --- a/docs/metadata.md +++ b/docs/metadata.md @@ -335,7 +335,18 @@ Uses `ModelMetadata` CRDs for P2P source metadata, `ConfigMap`s for tensor descr **ConfigMap name format**: `mx-source-{source_id}-{worker_id}-tensors-worker-{rank}` -ConfigMaps use `ownerReferences` pointing to the parent CRD so they are garbage-collected automatically. +When a client publishes a complete Kubernetes Pod identity (`POD_NAME`, +`POD_UID`, and `POD_NAMESPACE`) and the Pod is in the metadata namespace, the +`ModelMetadata` CR uses that Pod as an owner. This applies to both weight and +artifact metadata because both use the same `PublishMetadata` RPC. Deleting the +Pod therefore garbage-collects its `ModelMetadata` CR. Tensor `ConfigMap`s use a +second owner reference pointing to the parent CR, so they are collected with it. + +Kubernetes owner references cannot cross namespaces. If the identity is +missing, partial, or names a different namespace, publication still succeeds +without a Pod owner reference. This preserves behavior for older clients and +non-Kubernetes environments; the server-side stale metadata reaper remains the +cleanup path in those cases. **Model lifecycle CRD name format**: `mx-cache-{sanitized-model-name}-{hash}` @@ -353,6 +364,13 @@ apiVersion: modelexpress.nvidia.com/v1alpha1 kind: ModelMetadata metadata: name: mx-source-a1b2c3d4e5f67890-f3a2b1c4 + ownerReferences: + - apiVersion: v1 + kind: Pod + name: mx-vllm-7d9f8f6c8b-k2m4p + uid: 8c69d55f-3e40-4b6e-a16b-6f0c1168e171 + controller: false + blockOwnerDeletion: false labels: modelexpress.nvidia.com/mx-source-id: a1b2c3d4e5f67890 modelexpress.nvidia.com/mx-worker-id: f3a2b1c4 @@ -385,6 +403,13 @@ apiVersion: modelexpress.nvidia.com/v1alpha1 kind: ModelMetadata metadata: name: mx-source-b2c3d4e5f67890a1-f3a2b1c4 + ownerReferences: + - apiVersion: v1 + kind: Pod + name: mx-vllm-7d9f8f6c8b-k2m4p + uid: 8c69d55f-3e40-4b6e-a16b-6f0c1168e171 + controller: false + blockOwnerDeletion: false spec: modelName: artifact-transfer-e2e sourceType: deep_gemm_cache diff --git a/examples/p2p_transfer_k8s/client/README.md b/examples/p2p_transfer_k8s/client/README.md index 3f975ed8..a008a990 100644 --- a/examples/p2p_transfer_k8s/client/README.md +++ b/examples/p2p_transfer_k8s/client/README.md @@ -41,6 +41,16 @@ On startup, the engine-specific ModelExpress loader auto-detects the best loadin After loading, every worker publishes its metadata so future instances can discover it as an RDMA source. +When the server uses the Kubernetes metadata backend, the example manifests +also inject `POD_NAME`, `POD_UID`, and `POD_NAMESPACE` through the Downward API. +ModelExpress uses a complete same-namespace identity to make the Pod the owner +of each `ModelMetadata` CR it publishes, including artifact metadata. This +ownership behavior is independent of the inference engine. +Deleting the Pod then removes its metadata through Kubernetes garbage +collection. If any identity field is unavailable, publication continues without +an owner reference so older clients and non-Kubernetes deployments remain +compatible. + ## Prerequisites - ModelExpress server deployed (see [`../server/`](../server/)) diff --git a/examples/p2p_transfer_k8s/client/sglang/sglang-single-node-p2p.yaml b/examples/p2p_transfer_k8s/client/sglang/sglang-single-node-p2p.yaml index 6a2c5d0c..b34c6ad0 100644 --- a/examples/p2p_transfer_k8s/client/sglang/sglang-single-node-p2p.yaml +++ b/examples/p2p_transfer_k8s/client/sglang/sglang-single-node-p2p.yaml @@ -85,6 +85,18 @@ spec: port: 8000 timeoutSeconds: 10 env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: HF_HOME value: "/models" - name: MODEL_NAME diff --git a/examples/p2p_transfer_k8s/client/sglang/sglang-single-node-transfer-engine.yaml b/examples/p2p_transfer_k8s/client/sglang/sglang-single-node-transfer-engine.yaml index 37af3d3c..3ef04079 100644 --- a/examples/p2p_transfer_k8s/client/sglang/sglang-single-node-transfer-engine.yaml +++ b/examples/p2p_transfer_k8s/client/sglang/sglang-single-node-transfer-engine.yaml @@ -88,6 +88,18 @@ spec: port: 8000 timeoutSeconds: 10 env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: HF_HOME value: "/models" - name: MODEL_NAME diff --git a/examples/p2p_transfer_k8s/client/vllm/vllm-multi-node.yaml b/examples/p2p_transfer_k8s/client/vllm/vllm-multi-node.yaml index 01d96e47..b3d52c97 100644 --- a/examples/p2p_transfer_k8s/client/vllm/vllm-multi-node.yaml +++ b/examples/p2p_transfer_k8s/client/vllm/vllm-multi-node.yaml @@ -121,6 +121,14 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid - name: HF_TOKEN valueFrom: secretKeyRef: diff --git a/examples/p2p_transfer_k8s/client/vllm/vllm-single-node.yaml b/examples/p2p_transfer_k8s/client/vllm/vllm-single-node.yaml index 2f8bdb56..c771973d 100644 --- a/examples/p2p_transfer_k8s/client/vllm/vllm-single-node.yaml +++ b/examples/p2p_transfer_k8s/client/vllm/vllm-single-node.yaml @@ -95,6 +95,14 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid - name: HF_TOKEN valueFrom: secretKeyRef: diff --git a/modelexpress_client/go/gen/modelexpress/p2p/p2p.pb.go b/modelexpress_client/go/gen/modelexpress/p2p/p2p.pb.go index 4d71e109..b319d0b4 100644 --- a/modelexpress_client/go/gen/modelexpress/p2p/p2p.pb.go +++ b/modelexpress_client/go/gen/modelexpress/p2p/p2p.pb.go @@ -1939,12 +1939,15 @@ type PublishMetadataRequest struct { // If empty, the server rejects the request. WorkerId string `protobuf:"bytes,3,opt,name=worker_id,json=workerId,proto3" json:"worker_id,omitempty"` // Kubernetes pod name of the calling worker, set via the downward API. - // Used with pod_uid to set an ownerReference on the ModelMetadata CR - // so K8s garbage-collects it when the pod is deleted. - // When empty, no ownerReference is set. + // Used with pod_uid and pod_namespace to set an ownerReference on the + // ModelMetadata CR so K8s garbage-collects it when the pod is deleted. PodName string `protobuf:"bytes,4,opt,name=pod_name,json=podName,proto3" json:"pod_name,omitempty"` // Kubernetes pod UID of the calling worker, set via the downward API. - PodUid string `protobuf:"bytes,5,opt,name=pod_uid,json=podUid,proto3" json:"pod_uid,omitempty"` + PodUid string `protobuf:"bytes,5,opt,name=pod_uid,json=podUid,proto3" json:"pod_uid,omitempty"` + // Kubernetes namespace of the calling worker, set via the downward API. + // Owner references are only valid when this matches the ModelMetadata + // namespace. Empty means no ownerReference is set. + PodNamespace string `protobuf:"bytes,6,opt,name=pod_namespace,json=podNamespace,proto3" json:"pod_namespace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2014,6 +2017,13 @@ func (x *PublishMetadataRequest) GetPodUid() string { return "" } +func (x *PublishMetadataRequest) GetPodNamespace() string { + if x != nil { + return x.PodNamespace + } + return "" +} + type PublishMetadataResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` @@ -2701,13 +2711,14 @@ const file_p2p_proto_rawDesc = "" + "mxSourceId\x12\x1f\n" + "\vartifact_id\x18\x02 \x01(\tR\n" + "artifactId\x12>\n" + - "\x05chunk\x18\x03 \x01(\v2(.model_express.p2p.ArtifactManifestChunkR\x05chunk\"\xe3\x01\n" + + "\x05chunk\x18\x03 \x01(\v2(.model_express.p2p.ArtifactManifestChunkR\x05chunk\"\x88\x02\n" + "\x16PublishMetadataRequest\x12=\n" + "\bidentity\x18\x01 \x01(\v2!.model_express.p2p.SourceIdentityR\bidentity\x129\n" + "\x06worker\x18\x02 \x01(\v2!.model_express.p2p.WorkerMetadataR\x06worker\x12\x1b\n" + "\tworker_id\x18\x03 \x01(\tR\bworkerId\x12\x19\n" + "\bpod_name\x18\x04 \x01(\tR\apodName\x12\x17\n" + - "\apod_uid\x18\x05 \x01(\tR\x06podUid\"\x8c\x01\n" + + "\apod_uid\x18\x05 \x01(\tR\x06podUid\x12#\n" + + "\rpod_namespace\x18\x06 \x01(\tR\fpodNamespace\"\x8c\x01\n" + "\x17PublishMetadataResponse\x12\x18\n" + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + "\amessage\x18\x02 \x01(\tR\amessage\x12 \n" + diff --git a/modelexpress_client/python/modelexpress/client.py b/modelexpress_client/python/modelexpress/client.py index 0cbb5c8b..77eb1ba3 100644 --- a/modelexpress_client/python/modelexpress/client.py +++ b/modelexpress_client/python/modelexpress/client.py @@ -46,8 +46,6 @@ def publish_metadata( identity: "p2p_pb2.SourceIdentity", worker: "p2p_pb2.WorkerMetadata", worker_id: str, - pod_name: str = "", - pod_uid: str = "", ) -> str: """Publish worker metadata and return the computed mx_source_id.""" @@ -169,8 +167,6 @@ def publish_metadata( identity: "p2p_pb2.SourceIdentity", worker: "p2p_pb2.WorkerMetadata", worker_id: str, - pod_name: str = "", - pod_uid: str = "", ) -> str: """Publish metadata for one worker so targets can discover this source. @@ -180,8 +176,9 @@ def publish_metadata( identity=identity, worker=worker, worker_id=worker_id, - pod_name=pod_name, - pod_uid=pod_uid, + pod_name=envs.POD_NAME, + pod_uid=envs.POD_UID, + pod_namespace=envs.POD_NAMESPACE, ) response = self.stub.PublishMetadata(request, timeout=30) if not response.success: diff --git a/modelexpress_client/python/modelexpress/metadata/k8s_service_client.py b/modelexpress_client/python/modelexpress/metadata/k8s_service_client.py index d4259997..886321f5 100644 --- a/modelexpress_client/python/modelexpress/metadata/k8s_service_client.py +++ b/modelexpress_client/python/modelexpress/metadata/k8s_service_client.py @@ -104,8 +104,6 @@ def publish_metadata( identity: "p2p_pb2.SourceIdentity", worker: "p2p_pb2.WorkerMetadata", worker_id: str, - pod_name: str = "", - pod_uid: str = "", ) -> str: """Compute mx_source_id locally - there is no central store to hit. diff --git a/modelexpress_client/python/modelexpress/metadata/publish.py b/modelexpress_client/python/modelexpress/metadata/publish.py index c999a024..52c7a476 100644 --- a/modelexpress_client/python/modelexpress/metadata/publish.py +++ b/modelexpress_client/python/modelexpress/metadata/publish.py @@ -127,9 +127,6 @@ def publish_metadata_and_ready( f"[Worker {worker_rank}] Preparing {len(tensors)} tensors for model '{identity.model_name}'" ) - pod_name = envs.POD_NAME - pod_uid = envs.POD_UID - tensor_protos = build_tensor_protos(tensors, device_id, worker_rank) if _is_p2p_metadata_enabled(mx_client): @@ -174,8 +171,6 @@ def publish_fn() -> str: worker=worker, worker_id=worker_id, worker_rank=worker_rank, - pod_name=pod_name, - pod_uid=pod_uid, ) grpc_server.set_mx_source_id(mx_source_id) logger.info( @@ -209,8 +204,6 @@ def publish_fn() -> str: worker=worker, worker_id=worker_id, worker_rank=worker_rank, - pod_name=pod_name, - pod_uid=pod_uid, ) logger.info( f"[Worker {worker_rank}] Published metadata to MX server " @@ -238,18 +231,13 @@ def _publish_metadata_to_server( worker: "p2p_pb2.WorkerMetadata", worker_id: str, worker_rank: int, - pod_name: str = "", - pod_uid: str = "", ) -> str: """Publish metadata with bounded retries and exponential backoff.""" last_error: grpc.RpcError | None = None for attempt in range(1, PUBLISH_METADATA_MAX_ATTEMPTS + 1): try: - return mx_client.publish_metadata( - identity, worker, worker_id, - pod_name=pod_name, pod_uid=pod_uid, - ) + return mx_client.publish_metadata(identity, worker, worker_id) except grpc.RpcError as exc: if exc.code() not in PUBLISH_METADATA_RETRYABLE_STATUS_CODES: raise diff --git a/modelexpress_client/python/modelexpress/p2p_pb2.py b/modelexpress_client/python/modelexpress/p2p_pb2.py index 654da8cc..146098fe 100644 --- a/modelexpress_client/python/modelexpress/p2p_pb2.py +++ b/modelexpress_client/python/modelexpress/p2p_pb2.py @@ -27,7 +27,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tp2p.proto\x12\x11model_express.p2p\"\xe7\x04\n\x0eSourceIdentity\x12\x12\n\nmx_version\x18\x01 \x01(\t\x12\x37\n\x0emx_source_type\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\nmodel_name\x18\x03 \x01(\t\x12>\n\x11\x62\x61\x63kend_framework\x18\x04 \x01(\x0e\x32#.model_express.p2p.BackendFramework\x12\x1c\n\x14tensor_parallel_size\x18\x05 \x01(\r\x12\x1e\n\x16pipeline_parallel_size\x18\x06 \x01(\r\x12\x1c\n\x14\x65xpert_parallel_size\x18\x07 \x01(\r\x12\r\n\x05\x64type\x18\x08 \x01(\t\x12\x14\n\x0cquantization\x18\t \x01(\t\x12P\n\x10\x65xtra_parameters\x18\n \x03(\x0b\x32\x36.model_express.p2p.SourceIdentity.ExtraParametersEntry\x12\x10\n\x08revision\x18\x0b \x01(\t\x12!\n\x19\x62\x61\x63kend_framework_version\x18\x0c \x01(\t\x12\x15\n\rtorch_version\x18\r \x01(\t\x12\x14\n\x0c\x63uda_version\x18\x0e \x01(\t\x12\x16\n\x0etriton_version\x18\x0f \x01(\t\x12\x10\n\x08gpu_arch\x18\x10 \x01(\t\x12\x1d\n\x15\x63ompile_config_digest\x18\x11 \x01(\t\x1a\x36\n\x14\x45xtraParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"^\n\x10TensorDescriptor\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x61\x64\x64r\x18\x02 \x01(\x04\x12\x0c\n\x04size\x18\x03 \x01(\x04\x12\x11\n\tdevice_id\x18\x04 \x01(\r\x12\r\n\x05\x64type\x18\x05 \x01(\t\"}\n\x16\x41rtifactSourceMetadata\x12\x13\n\x0b\x61rtifact_id\x18\x01 \x01(\t\x12\x12\n\ntotal_size\x18\x02 \x01(\x04\x12\x12\n\nfile_count\x18\x03 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x04 \x01(\r\x12\x11\n\tnode_rank\x18\x05 \x01(\r\"L\n\x14TensorSourceMetadata\x12\x34\n\x07tensors\x18\x01 \x03(\x0b\x32#.model_express.p2p.TensorDescriptor\"\xeb\x01\n\x10\x41rtifactManifest\x12\x18\n\x10manifest_version\x18\x01 \x01(\r\x12\x37\n\x0emx_source_type\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\nchunk_size\x18\x03 \x01(\x04\x12\x36\n\x05\x66iles\x18\x04 \x03(\x0b\x32\'.model_express.p2p.ArtifactManifestFile\x12\x38\n\x06\x63hunks\x18\x05 \x03(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\"X\n\x14\x41rtifactManifestFile\x12\x12\n\nfile_index\x18\x01 \x01(\r\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\x0c\n\x04size\x18\x03 \x01(\x04\x12\x10\n\x08\x63hecksum\x18\x04 \x01(\t\"w\n\x15\x41rtifactManifestChunk\x12\x13\n\x0b\x63hunk_index\x18\x01 \x01(\r\x12\x12\n\nfile_index\x18\x02 \x01(\r\x12\x13\n\x0b\x66ile_offset\x18\x03 \x01(\x04\x12\x0e\n\x06length\x18\x04 \x01(\x04\x12\x10\n\x08\x63hecksum\x18\x05 \x01(\t\"\xf3\x03\n\x0eWorkerMetadata\x12\x13\n\x0bworker_rank\x18\x01 \x01(\r\x12\x17\n\rnixl_metadata\x18\x02 \x01(\x0cH\x00\x12$\n\x1atransfer_engine_session_id\x18\n \x01(\tH\x00\x12\x38\n\x07tensors\x18\x03 \x03(\x0b\x32#.model_express.p2p.TensorDescriptorB\x02\x18\x01\x12/\n\x06status\x18\x04 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatus\x12\x12\n\nupdated_at\x18\x05 \x01(\x03\x12\x19\n\x11metadata_endpoint\x18\x06 \x01(\t\x12\x12\n\nagent_name\x18\x07 \x01(\t\x12\x1c\n\x14worker_grpc_endpoint\x18\x08 \x01(\t\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\t \x01(\t\x12@\n\rtensor_source\x18\x14 \x01(\x0b\x32\'.model_express.p2p.TensorSourceMetadataH\x01\x12\x44\n\x0f\x61rtifact_source\x18\x15 \x01(\x0b\x32).model_express.p2p.ArtifactSourceMetadataH\x01\x42\x12\n\x10\x62\x61\x63kend_metadataB\x10\n\x0esource_payload\"V\n\x18GetTensorManifestRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x16\n\tworker_id\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_worker_id\"\xe6\x01\n\x19GetTensorManifestResponse\x12\x34\n\x07tensors\x18\x01 \x03(\x0b\x32#.model_express.p2p.TensorDescriptor\x12\x14\n\x0cmx_source_id\x18\x02 \x01(\t\x12\x19\n\x11metadata_endpoint\x18\x03 \x01(\t\x12\x12\n\nagent_name\x18\x04 \x01(\t\x12\x13\n\x0bworker_rank\x18\x05 \x01(\r\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\x06 \x01(\t\x12\x16\n\tworker_id\x18\x07 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_worker_id\"M\n GetArtifactManifestHeaderRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\"\xee\x02\n!GetArtifactManifestHeaderResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x18\n\x10manifest_version\x18\x03 \x01(\r\x12\x37\n\x0emx_source_type\x18\x04 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\ntotal_size\x18\x05 \x01(\x04\x12\x12\n\nfile_count\x18\x06 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x07 \x01(\r\x12\x12\n\nchunk_size\x18\x08 \x01(\x04\x12\x19\n\x11metadata_endpoint\x18\t \x01(\t\x12\x12\n\nagent_name\x18\n \x01(\t\x12\x13\n\x0bworker_rank\x18\x0b \x01(\r\x12\x36\n\x05\x66iles\x18\x0c \x03(\x0b\x32\'.model_express.p2p.ArtifactManifestFile\"|\n GetArtifactManifestChunksRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x19\n\x11start_chunk_index\x18\x03 \x01(\r\x12\x12\n\nmax_chunks\x18\x04 \x01(\r\"\xbc\x01\n!GetArtifactManifestChunksResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x19\n\x11start_chunk_index\x18\x03 \x01(\r\x12\x38\n\x06\x63hunks\x18\x04 \x03(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\x12\x17\n\x0fnext_page_token\x18\x05 \x01(\t\"R\n\x1f\x41rtifactChunkTransferDescriptor\x12\x0c\n\x04\x61\x64\x64r\x18\x01 \x01(\x04\x12\x0e\n\x06length\x18\x02 \x01(\x04\x12\x11\n\tdevice_id\x18\x03 \x01(\r\"]\n\x1bPrepareArtifactChunkRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x13\n\x0b\x63hunk_index\x18\x03 \x01(\r\"\xf1\x01\n\x1cPrepareArtifactChunkResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x10\n\x08lease_id\x18\x03 \x01(\t\x12\x37\n\x05\x63hunk\x18\x04 \x01(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\x12\x42\n\x06source\x18\x05 \x01(\x0b\x32\x32.model_express.p2p.ArtifactChunkTransferDescriptor\x12\x17\n\x0fsource_metadata\x18\x06 \x01(\x0c\"Z\n\x1bReleaseArtifactChunkRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x10\n\x08lease_id\x18\x03 \x01(\t\"\x82\x01\n\x1cReleaseArtifactChunkResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x37\n\x05\x63hunk\x18\x03 \x01(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\"\xb6\x01\n\x16PublishMetadataRequest\x12\x33\n\x08identity\x18\x01 \x01(\x0b\x32!.model_express.p2p.SourceIdentity\x12\x31\n\x06worker\x18\x02 \x01(\x0b\x32!.model_express.p2p.WorkerMetadata\x12\x11\n\tworker_id\x18\x03 \x01(\t\x12\x10\n\x08pod_name\x18\x04 \x01(\t\x12\x0f\n\x07pod_uid\x18\x05 \x01(\t\"d\n\x17PublishMetadataResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x14\n\x0cmx_source_id\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\"z\n\x11SourceInstanceRef\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x11\n\tworker_id\x18\x02 \x01(\t\x12\x12\n\nmodel_name\x18\x03 \x01(\t\x12\x13\n\x0bworker_rank\x18\x04 \x01(\r\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\x05 \x01(\t\"\x98\x01\n\x12ListSourcesRequest\x12\x33\n\x08identity\x18\x01 \x01(\x0b\x32!.model_express.p2p.SourceIdentity\x12;\n\rstatus_filter\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatusH\x00\x88\x01\x01\x42\x10\n\x0e_status_filter\"N\n\x13ListSourcesResponse\x12\x37\n\tinstances\x18\x01 \x03(\x0b\x32$.model_express.p2p.SourceInstanceRef\"=\n\x12GetMetadataRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x11\n\tworker_id\x18\x02 \x01(\t\"\x80\x01\n\x13GetMetadataResponse\x12\r\n\x05\x66ound\x18\x01 \x01(\x08\x12\x31\n\x06worker\x18\x02 \x01(\x0b\x32!.model_express.p2p.WorkerMetadata\x12\x14\n\x0cmx_source_id\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\"\x84\x01\n\x13UpdateStatusRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0bworker_rank\x18\x02 \x01(\r\x12/\n\x06status\x18\x03 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatus\x12\x11\n\tworker_id\x18\x04 \x01(\t\"8\n\x14UpdateStatusResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t*\x8a\x01\n\x10\x42\x61\x63kendFramework\x12\x1d\n\x19\x42\x41\x43KEND_FRAMEWORK_UNKNOWN\x10\x00\x12\x1a\n\x16\x42\x41\x43KEND_FRAMEWORK_VLLM\x10\x01\x12\x1c\n\x18\x42\x41\x43KEND_FRAMEWORK_SGLANG\x10\x02\x12\x1d\n\x19\x42\x41\x43KEND_FRAMEWORK_TRT_LLM\x10\x03*\xba\x02\n\x0cMxSourceType\x12\x1a\n\x16MX_SOURCE_TYPE_WEIGHTS\x10\x00\x12\x17\n\x13MX_SOURCE_TYPE_LORA\x10\x01\x12\x1d\n\x19MX_SOURCE_TYPE_CUDA_GRAPH\x10\x02\x12&\n\"MX_SOURCE_TYPE_TORCH_COMPILE_CACHE\x10\x03\x12\x1f\n\x1bMX_SOURCE_TYPE_TRITON_CACHE\x10\x04\x12\"\n\x1eMX_SOURCE_TYPE_DEEP_GEMM_CACHE\x10\x05\x12!\n\x1dMX_SOURCE_TYPE_TILELANG_CACHE\x10\x06\x12!\n\x1dMX_SOURCE_TYPE_CUTE_DSL_CACHE\x10\x07\x12#\n\x1fMX_SOURCE_TYPE_FLASHINFER_CACHE\x10\x08*{\n\x0cSourceStatus\x12\x19\n\x15SOURCE_STATUS_UNKNOWN\x10\x00\x12\x1e\n\x1aSOURCE_STATUS_INITIALIZING\x10\x01\x12\x17\n\x13SOURCE_STATUS_READY\x10\x02\x12\x17\n\x13SOURCE_STATUS_STALE\x10\x03\x32\x93\x03\n\nP2pService\x12h\n\x0fPublishMetadata\x12).model_express.p2p.PublishMetadataRequest\x1a*.model_express.p2p.PublishMetadataResponse\x12\\\n\x0bListSources\x12%.model_express.p2p.ListSourcesRequest\x1a&.model_express.p2p.ListSourcesResponse\x12\\\n\x0bGetMetadata\x12%.model_express.p2p.GetMetadataRequest\x1a&.model_express.p2p.GetMetadataResponse\x12_\n\x0cUpdateStatus\x12&.model_express.p2p.UpdateStatusRequest\x1a\'.model_express.p2p.UpdateStatusResponse2\x83\x05\n\rWorkerService\x12n\n\x11GetTensorManifest\x12+.model_express.p2p.GetTensorManifestRequest\x1a,.model_express.p2p.GetTensorManifestResponse\x12\x86\x01\n\x19GetArtifactManifestHeader\x12\x33.model_express.p2p.GetArtifactManifestHeaderRequest\x1a\x34.model_express.p2p.GetArtifactManifestHeaderResponse\x12\x86\x01\n\x19GetArtifactManifestChunks\x12\x33.model_express.p2p.GetArtifactManifestChunksRequest\x1a\x34.model_express.p2p.GetArtifactManifestChunksResponse\x12w\n\x14PrepareArtifactChunk\x12..model_express.p2p.PrepareArtifactChunkRequest\x1a/.model_express.p2p.PrepareArtifactChunkResponse\x12w\n\x14ReleaseArtifactChunk\x12..model_express.p2p.ReleaseArtifactChunkRequest\x1a/.model_express.p2p.ReleaseArtifactChunkResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tp2p.proto\x12\x11model_express.p2p\"\xe7\x04\n\x0eSourceIdentity\x12\x12\n\nmx_version\x18\x01 \x01(\t\x12\x37\n\x0emx_source_type\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\nmodel_name\x18\x03 \x01(\t\x12>\n\x11\x62\x61\x63kend_framework\x18\x04 \x01(\x0e\x32#.model_express.p2p.BackendFramework\x12\x1c\n\x14tensor_parallel_size\x18\x05 \x01(\r\x12\x1e\n\x16pipeline_parallel_size\x18\x06 \x01(\r\x12\x1c\n\x14\x65xpert_parallel_size\x18\x07 \x01(\r\x12\r\n\x05\x64type\x18\x08 \x01(\t\x12\x14\n\x0cquantization\x18\t \x01(\t\x12P\n\x10\x65xtra_parameters\x18\n \x03(\x0b\x32\x36.model_express.p2p.SourceIdentity.ExtraParametersEntry\x12\x10\n\x08revision\x18\x0b \x01(\t\x12!\n\x19\x62\x61\x63kend_framework_version\x18\x0c \x01(\t\x12\x15\n\rtorch_version\x18\r \x01(\t\x12\x14\n\x0c\x63uda_version\x18\x0e \x01(\t\x12\x16\n\x0etriton_version\x18\x0f \x01(\t\x12\x10\n\x08gpu_arch\x18\x10 \x01(\t\x12\x1d\n\x15\x63ompile_config_digest\x18\x11 \x01(\t\x1a\x36\n\x14\x45xtraParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"^\n\x10TensorDescriptor\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x61\x64\x64r\x18\x02 \x01(\x04\x12\x0c\n\x04size\x18\x03 \x01(\x04\x12\x11\n\tdevice_id\x18\x04 \x01(\r\x12\r\n\x05\x64type\x18\x05 \x01(\t\"}\n\x16\x41rtifactSourceMetadata\x12\x13\n\x0b\x61rtifact_id\x18\x01 \x01(\t\x12\x12\n\ntotal_size\x18\x02 \x01(\x04\x12\x12\n\nfile_count\x18\x03 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x04 \x01(\r\x12\x11\n\tnode_rank\x18\x05 \x01(\r\"L\n\x14TensorSourceMetadata\x12\x34\n\x07tensors\x18\x01 \x03(\x0b\x32#.model_express.p2p.TensorDescriptor\"\xeb\x01\n\x10\x41rtifactManifest\x12\x18\n\x10manifest_version\x18\x01 \x01(\r\x12\x37\n\x0emx_source_type\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\nchunk_size\x18\x03 \x01(\x04\x12\x36\n\x05\x66iles\x18\x04 \x03(\x0b\x32\'.model_express.p2p.ArtifactManifestFile\x12\x38\n\x06\x63hunks\x18\x05 \x03(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\"X\n\x14\x41rtifactManifestFile\x12\x12\n\nfile_index\x18\x01 \x01(\r\x12\x0c\n\x04path\x18\x02 \x01(\t\x12\x0c\n\x04size\x18\x03 \x01(\x04\x12\x10\n\x08\x63hecksum\x18\x04 \x01(\t\"w\n\x15\x41rtifactManifestChunk\x12\x13\n\x0b\x63hunk_index\x18\x01 \x01(\r\x12\x12\n\nfile_index\x18\x02 \x01(\r\x12\x13\n\x0b\x66ile_offset\x18\x03 \x01(\x04\x12\x0e\n\x06length\x18\x04 \x01(\x04\x12\x10\n\x08\x63hecksum\x18\x05 \x01(\t\"\xf3\x03\n\x0eWorkerMetadata\x12\x13\n\x0bworker_rank\x18\x01 \x01(\r\x12\x17\n\rnixl_metadata\x18\x02 \x01(\x0cH\x00\x12$\n\x1atransfer_engine_session_id\x18\n \x01(\tH\x00\x12\x38\n\x07tensors\x18\x03 \x03(\x0b\x32#.model_express.p2p.TensorDescriptorB\x02\x18\x01\x12/\n\x06status\x18\x04 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatus\x12\x12\n\nupdated_at\x18\x05 \x01(\x03\x12\x19\n\x11metadata_endpoint\x18\x06 \x01(\t\x12\x12\n\nagent_name\x18\x07 \x01(\t\x12\x1c\n\x14worker_grpc_endpoint\x18\x08 \x01(\t\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\t \x01(\t\x12@\n\rtensor_source\x18\x14 \x01(\x0b\x32\'.model_express.p2p.TensorSourceMetadataH\x01\x12\x44\n\x0f\x61rtifact_source\x18\x15 \x01(\x0b\x32).model_express.p2p.ArtifactSourceMetadataH\x01\x42\x12\n\x10\x62\x61\x63kend_metadataB\x10\n\x0esource_payload\"V\n\x18GetTensorManifestRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x16\n\tworker_id\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_worker_id\"\xe6\x01\n\x19GetTensorManifestResponse\x12\x34\n\x07tensors\x18\x01 \x03(\x0b\x32#.model_express.p2p.TensorDescriptor\x12\x14\n\x0cmx_source_id\x18\x02 \x01(\t\x12\x19\n\x11metadata_endpoint\x18\x03 \x01(\t\x12\x12\n\nagent_name\x18\x04 \x01(\t\x12\x13\n\x0bworker_rank\x18\x05 \x01(\r\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\x06 \x01(\t\x12\x16\n\tworker_id\x18\x07 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_worker_id\"M\n GetArtifactManifestHeaderRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\"\xee\x02\n!GetArtifactManifestHeaderResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x18\n\x10manifest_version\x18\x03 \x01(\r\x12\x37\n\x0emx_source_type\x18\x04 \x01(\x0e\x32\x1f.model_express.p2p.MxSourceType\x12\x12\n\ntotal_size\x18\x05 \x01(\x04\x12\x12\n\nfile_count\x18\x06 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x07 \x01(\r\x12\x12\n\nchunk_size\x18\x08 \x01(\x04\x12\x19\n\x11metadata_endpoint\x18\t \x01(\t\x12\x12\n\nagent_name\x18\n \x01(\t\x12\x13\n\x0bworker_rank\x18\x0b \x01(\r\x12\x36\n\x05\x66iles\x18\x0c \x03(\x0b\x32\'.model_express.p2p.ArtifactManifestFile\"|\n GetArtifactManifestChunksRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x19\n\x11start_chunk_index\x18\x03 \x01(\r\x12\x12\n\nmax_chunks\x18\x04 \x01(\r\"\xbc\x01\n!GetArtifactManifestChunksResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x19\n\x11start_chunk_index\x18\x03 \x01(\r\x12\x38\n\x06\x63hunks\x18\x04 \x03(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\x12\x17\n\x0fnext_page_token\x18\x05 \x01(\t\"R\n\x1f\x41rtifactChunkTransferDescriptor\x12\x0c\n\x04\x61\x64\x64r\x18\x01 \x01(\x04\x12\x0e\n\x06length\x18\x02 \x01(\x04\x12\x11\n\tdevice_id\x18\x03 \x01(\r\"]\n\x1bPrepareArtifactChunkRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x13\n\x0b\x63hunk_index\x18\x03 \x01(\r\"\xf1\x01\n\x1cPrepareArtifactChunkResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x10\n\x08lease_id\x18\x03 \x01(\t\x12\x37\n\x05\x63hunk\x18\x04 \x01(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\x12\x42\n\x06source\x18\x05 \x01(\x0b\x32\x32.model_express.p2p.ArtifactChunkTransferDescriptor\x12\x17\n\x0fsource_metadata\x18\x06 \x01(\x0c\"Z\n\x1bReleaseArtifactChunkRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x10\n\x08lease_id\x18\x03 \x01(\t\"\x82\x01\n\x1cReleaseArtifactChunkResponse\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x02 \x01(\t\x12\x37\n\x05\x63hunk\x18\x03 \x01(\x0b\x32(.model_express.p2p.ArtifactManifestChunk\"\xcd\x01\n\x16PublishMetadataRequest\x12\x33\n\x08identity\x18\x01 \x01(\x0b\x32!.model_express.p2p.SourceIdentity\x12\x31\n\x06worker\x18\x02 \x01(\x0b\x32!.model_express.p2p.WorkerMetadata\x12\x11\n\tworker_id\x18\x03 \x01(\t\x12\x10\n\x08pod_name\x18\x04 \x01(\t\x12\x0f\n\x07pod_uid\x18\x05 \x01(\t\x12\x15\n\rpod_namespace\x18\x06 \x01(\t\"d\n\x17PublishMetadataResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x14\n\x0cmx_source_id\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\"z\n\x11SourceInstanceRef\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x11\n\tworker_id\x18\x02 \x01(\t\x12\x12\n\nmodel_name\x18\x03 \x01(\t\x12\x13\n\x0bworker_rank\x18\x04 \x01(\r\x12\x13\n\x0b\x61\x63\x63\x65lerator\x18\x05 \x01(\t\"\x98\x01\n\x12ListSourcesRequest\x12\x33\n\x08identity\x18\x01 \x01(\x0b\x32!.model_express.p2p.SourceIdentity\x12;\n\rstatus_filter\x18\x02 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatusH\x00\x88\x01\x01\x42\x10\n\x0e_status_filter\"N\n\x13ListSourcesResponse\x12\x37\n\tinstances\x18\x01 \x03(\x0b\x32$.model_express.p2p.SourceInstanceRef\"=\n\x12GetMetadataRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x11\n\tworker_id\x18\x02 \x01(\t\"\x80\x01\n\x13GetMetadataResponse\x12\r\n\x05\x66ound\x18\x01 \x01(\x08\x12\x31\n\x06worker\x18\x02 \x01(\x0b\x32!.model_express.p2p.WorkerMetadata\x12\x14\n\x0cmx_source_id\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\"\x84\x01\n\x13UpdateStatusRequest\x12\x14\n\x0cmx_source_id\x18\x01 \x01(\t\x12\x13\n\x0bworker_rank\x18\x02 \x01(\r\x12/\n\x06status\x18\x03 \x01(\x0e\x32\x1f.model_express.p2p.SourceStatus\x12\x11\n\tworker_id\x18\x04 \x01(\t\"8\n\x14UpdateStatusResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t*\x8a\x01\n\x10\x42\x61\x63kendFramework\x12\x1d\n\x19\x42\x41\x43KEND_FRAMEWORK_UNKNOWN\x10\x00\x12\x1a\n\x16\x42\x41\x43KEND_FRAMEWORK_VLLM\x10\x01\x12\x1c\n\x18\x42\x41\x43KEND_FRAMEWORK_SGLANG\x10\x02\x12\x1d\n\x19\x42\x41\x43KEND_FRAMEWORK_TRT_LLM\x10\x03*\xba\x02\n\x0cMxSourceType\x12\x1a\n\x16MX_SOURCE_TYPE_WEIGHTS\x10\x00\x12\x17\n\x13MX_SOURCE_TYPE_LORA\x10\x01\x12\x1d\n\x19MX_SOURCE_TYPE_CUDA_GRAPH\x10\x02\x12&\n\"MX_SOURCE_TYPE_TORCH_COMPILE_CACHE\x10\x03\x12\x1f\n\x1bMX_SOURCE_TYPE_TRITON_CACHE\x10\x04\x12\"\n\x1eMX_SOURCE_TYPE_DEEP_GEMM_CACHE\x10\x05\x12!\n\x1dMX_SOURCE_TYPE_TILELANG_CACHE\x10\x06\x12!\n\x1dMX_SOURCE_TYPE_CUTE_DSL_CACHE\x10\x07\x12#\n\x1fMX_SOURCE_TYPE_FLASHINFER_CACHE\x10\x08*{\n\x0cSourceStatus\x12\x19\n\x15SOURCE_STATUS_UNKNOWN\x10\x00\x12\x1e\n\x1aSOURCE_STATUS_INITIALIZING\x10\x01\x12\x17\n\x13SOURCE_STATUS_READY\x10\x02\x12\x17\n\x13SOURCE_STATUS_STALE\x10\x03\x32\x93\x03\n\nP2pService\x12h\n\x0fPublishMetadata\x12).model_express.p2p.PublishMetadataRequest\x1a*.model_express.p2p.PublishMetadataResponse\x12\\\n\x0bListSources\x12%.model_express.p2p.ListSourcesRequest\x1a&.model_express.p2p.ListSourcesResponse\x12\\\n\x0bGetMetadata\x12%.model_express.p2p.GetMetadataRequest\x1a&.model_express.p2p.GetMetadataResponse\x12_\n\x0cUpdateStatus\x12&.model_express.p2p.UpdateStatusRequest\x1a\'.model_express.p2p.UpdateStatusResponse2\x83\x05\n\rWorkerService\x12n\n\x11GetTensorManifest\x12+.model_express.p2p.GetTensorManifestRequest\x1a,.model_express.p2p.GetTensorManifestResponse\x12\x86\x01\n\x19GetArtifactManifestHeader\x12\x33.model_express.p2p.GetArtifactManifestHeaderRequest\x1a\x34.model_express.p2p.GetArtifactManifestHeaderResponse\x12\x86\x01\n\x19GetArtifactManifestChunks\x12\x33.model_express.p2p.GetArtifactManifestChunksRequest\x1a\x34.model_express.p2p.GetArtifactManifestChunksResponse\x12w\n\x14PrepareArtifactChunk\x12..model_express.p2p.PrepareArtifactChunkRequest\x1a/.model_express.p2p.PrepareArtifactChunkResponse\x12w\n\x14ReleaseArtifactChunk\x12..model_express.p2p.ReleaseArtifactChunkRequest\x1a/.model_express.p2p.ReleaseArtifactChunkResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -38,12 +38,12 @@ _globals['_SOURCEIDENTITY_EXTRAPARAMETERSENTRY']._serialized_options = b'8\001' _globals['_WORKERMETADATA'].fields_by_name['tensors']._loaded_options = None _globals['_WORKERMETADATA'].fields_by_name['tensors']._serialized_options = b'\030\001' - _globals['_BACKENDFRAMEWORK']._serialized_start=4670 - _globals['_BACKENDFRAMEWORK']._serialized_end=4808 - _globals['_MXSOURCETYPE']._serialized_start=4811 - _globals['_MXSOURCETYPE']._serialized_end=5125 - _globals['_SOURCESTATUS']._serialized_start=5127 - _globals['_SOURCESTATUS']._serialized_end=5250 + _globals['_BACKENDFRAMEWORK']._serialized_start=4693 + _globals['_BACKENDFRAMEWORK']._serialized_end=4831 + _globals['_MXSOURCETYPE']._serialized_start=4834 + _globals['_MXSOURCETYPE']._serialized_end=5148 + _globals['_SOURCESTATUS']._serialized_start=5150 + _globals['_SOURCESTATUS']._serialized_end=5273 _globals['_SOURCEIDENTITY']._serialized_start=33 _globals['_SOURCEIDENTITY']._serialized_end=648 _globals['_SOURCEIDENTITY_EXTRAPARAMETERSENTRY']._serialized_start=594 @@ -85,25 +85,25 @@ _globals['_RELEASEARTIFACTCHUNKRESPONSE']._serialized_start=3504 _globals['_RELEASEARTIFACTCHUNKRESPONSE']._serialized_end=3634 _globals['_PUBLISHMETADATAREQUEST']._serialized_start=3637 - _globals['_PUBLISHMETADATAREQUEST']._serialized_end=3819 - _globals['_PUBLISHMETADATARESPONSE']._serialized_start=3821 - _globals['_PUBLISHMETADATARESPONSE']._serialized_end=3921 - _globals['_SOURCEINSTANCEREF']._serialized_start=3923 - _globals['_SOURCEINSTANCEREF']._serialized_end=4045 - _globals['_LISTSOURCESREQUEST']._serialized_start=4048 - _globals['_LISTSOURCESREQUEST']._serialized_end=4200 - _globals['_LISTSOURCESRESPONSE']._serialized_start=4202 - _globals['_LISTSOURCESRESPONSE']._serialized_end=4280 - _globals['_GETMETADATAREQUEST']._serialized_start=4282 - _globals['_GETMETADATAREQUEST']._serialized_end=4343 - _globals['_GETMETADATARESPONSE']._serialized_start=4346 - _globals['_GETMETADATARESPONSE']._serialized_end=4474 - _globals['_UPDATESTATUSREQUEST']._serialized_start=4477 - _globals['_UPDATESTATUSREQUEST']._serialized_end=4609 - _globals['_UPDATESTATUSRESPONSE']._serialized_start=4611 - _globals['_UPDATESTATUSRESPONSE']._serialized_end=4667 - _globals['_P2PSERVICE']._serialized_start=5253 - _globals['_P2PSERVICE']._serialized_end=5656 - _globals['_WORKERSERVICE']._serialized_start=5659 - _globals['_WORKERSERVICE']._serialized_end=6302 + _globals['_PUBLISHMETADATAREQUEST']._serialized_end=3842 + _globals['_PUBLISHMETADATARESPONSE']._serialized_start=3844 + _globals['_PUBLISHMETADATARESPONSE']._serialized_end=3944 + _globals['_SOURCEINSTANCEREF']._serialized_start=3946 + _globals['_SOURCEINSTANCEREF']._serialized_end=4068 + _globals['_LISTSOURCESREQUEST']._serialized_start=4071 + _globals['_LISTSOURCESREQUEST']._serialized_end=4223 + _globals['_LISTSOURCESRESPONSE']._serialized_start=4225 + _globals['_LISTSOURCESRESPONSE']._serialized_end=4303 + _globals['_GETMETADATAREQUEST']._serialized_start=4305 + _globals['_GETMETADATAREQUEST']._serialized_end=4366 + _globals['_GETMETADATARESPONSE']._serialized_start=4369 + _globals['_GETMETADATARESPONSE']._serialized_end=4497 + _globals['_UPDATESTATUSREQUEST']._serialized_start=4500 + _globals['_UPDATESTATUSREQUEST']._serialized_end=4632 + _globals['_UPDATESTATUSRESPONSE']._serialized_start=4634 + _globals['_UPDATESTATUSRESPONSE']._serialized_end=4690 + _globals['_P2PSERVICE']._serialized_start=5276 + _globals['_P2PSERVICE']._serialized_end=5679 + _globals['_WORKERSERVICE']._serialized_start=5682 + _globals['_WORKERSERVICE']._serialized_end=6325 # @@protoc_insertion_point(module_scope) diff --git a/modelexpress_client/python/tests/test_artifact_transfer.py b/modelexpress_client/python/tests/test_artifact_transfer.py index 0742143a..8400b582 100644 --- a/modelexpress_client/python/tests/test_artifact_transfer.py +++ b/modelexpress_client/python/tests/test_artifact_transfer.py @@ -1366,18 +1366,14 @@ def __init__(self, mx_source_id: str | None = None): self.published_identity = None self.published_worker = None self.published_worker_id = "" - self.published_pod_name = "" - self.published_pod_uid = "" self.status_updates = [] - def publish_metadata(self, identity, worker, worker_id, pod_name="", pod_uid=""): + def publish_metadata(self, identity, worker, worker_id): self.published_identity = p2p_pb2.SourceIdentity() self.published_identity.CopyFrom(identity) self.published_worker = p2p_pb2.WorkerMetadata() self.published_worker.CopyFrom(worker) self.published_worker_id = worker_id - self.published_pod_name = pod_name - self.published_pod_uid = pod_uid return self.mx_source_id or compute_mx_source_id(identity) def list_sources(self, identity=None, status_filter=None): diff --git a/modelexpress_client/python/tests/test_client.py b/modelexpress_client/python/tests/test_client.py new file mode 100644 index 00000000..729a087b --- /dev/null +++ b/modelexpress_client/python/tests/test_client.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import MagicMock + +from modelexpress import p2p_pb2 +from modelexpress.client import MxClient + + +def test_publish_metadata_adds_pod_identity_without_changing_public_api(monkeypatch): + monkeypatch.setenv("POD_NAME", "source-0") + monkeypatch.setenv("POD_UID", "pod-uid") + monkeypatch.setenv("POD_NAMESPACE", "default") + stub = MagicMock() + stub.PublishMetadata.return_value = p2p_pb2.PublishMetadataResponse( + success=True, + mx_source_id="source-id", + ) + client = MxClient("localhost:8001") + client._channel = MagicMock() + client._stub = stub + + source_id = client.publish_metadata( + p2p_pb2.SourceIdentity(model_name="model"), + p2p_pb2.WorkerMetadata(worker_rank=0), + "worker-id", + ) + + assert source_id == "source-id" + request = stub.PublishMetadata.call_args.args[0] + assert request.pod_name == "source-0" + assert request.pod_uid == "pod-uid" + assert request.pod_namespace == "default" diff --git a/modelexpress_client/python/tests/test_types.py b/modelexpress_client/python/tests/test_types.py index 4328afa3..a629937e 100644 --- a/modelexpress_client/python/tests/test_types.py +++ b/modelexpress_client/python/tests/test_types.py @@ -6,6 +6,7 @@ import re from google.protobuf import __version__ as _pb_version +from google.protobuf import descriptor_pb2, descriptor_pool, message_factory from modelexpress import p2p_pb2 from modelexpress.metadata.payload import ( @@ -36,6 +37,54 @@ def test_p2p_pb2_gencode_matches_runtime_major_version(self): f"../../modelexpress_common/proto/p2p.proto" ) + def test_publish_request_is_readable_by_0_4_schema(self): + legacy_file = descriptor_pb2.FileDescriptorProto( + name="modelexpress_0_4_publish_metadata.proto", + package="model_express.p2p.compat_0_4", + dependency=[p2p_pb2.DESCRIPTOR.name], + syntax="proto3", + ) + request = legacy_file.message_type.add(name="PublishMetadataRequest") + for name, number, type_name in ( + ("identity", 1, ".model_express.p2p.SourceIdentity"), + ("worker", 2, ".model_express.p2p.WorkerMetadata"), + ): + request.field.add( + name=name, + number=number, + label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, + type=descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE, + type_name=type_name, + ) + request.field.add( + name="worker_id", + number=3, + label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, + type=descriptor_pb2.FieldDescriptorProto.TYPE_STRING, + ) + + pool = descriptor_pool.Default() + pool.Add(legacy_file) + legacy_request = message_factory.GetMessageClass( + pool.FindMessageTypeByName( + "model_express.p2p.compat_0_4.PublishMetadataRequest" + ) + )() + current_request = p2p_pb2.PublishMetadataRequest( + identity=p2p_pb2.SourceIdentity(model_name="model"), + worker=p2p_pb2.WorkerMetadata(worker_rank=1), + worker_id="worker", + pod_name="source-0", + pod_uid="pod-uid", + pod_namespace="default", + ) + + legacy_request.ParseFromString(current_request.SerializeToString()) + + assert legacy_request.identity.model_name == "model" + assert legacy_request.worker.worker_rank == 1 + assert legacy_request.worker_id == "worker" + class TestTensorDescriptor: """Tests for TensorDescriptor dataclass.""" diff --git a/modelexpress_common/proto/p2p.proto b/modelexpress_common/proto/p2p.proto index 435e84db..253197e1 100644 --- a/modelexpress_common/proto/p2p.proto +++ b/modelexpress_common/proto/p2p.proto @@ -501,13 +501,17 @@ message PublishMetadataRequest { string worker_id = 3; // Kubernetes pod name of the calling worker, set via the downward API. - // Used with pod_uid to set an ownerReference on the ModelMetadata CR - // so K8s garbage-collects it when the pod is deleted. - // When empty, no ownerReference is set. + // Used with pod_uid and pod_namespace to set an ownerReference on the + // ModelMetadata CR so K8s garbage-collects it when the pod is deleted. string pod_name = 4; // Kubernetes pod UID of the calling worker, set via the downward API. string pod_uid = 5; + + // Kubernetes namespace of the calling worker, set via the downward API. + // Owner references are only valid when this matches the ModelMetadata + // namespace. Empty means no ownerReference is set. + string pod_namespace = 6; } message PublishMetadataResponse { diff --git a/modelexpress_server/src/p2p/backend.rs b/modelexpress_server/src/p2p/backend.rs index c00f6408..b050c14b 100644 --- a/modelexpress_server/src/p2p/backend.rs +++ b/modelexpress_server/src/p2p/backend.rs @@ -298,6 +298,7 @@ pub trait MetadataBackend: Send + Sync { worker: WorkerMetadata, pod_name: &str, pod_uid: &str, + pod_namespace: &str, ) -> MetadataResult<()>; /// Get full tensor metadata for one specific worker. diff --git a/modelexpress_server/src/p2p/backend/kubernetes.rs b/modelexpress_server/src/p2p/backend/kubernetes.rs index 2cbb7b0c..56f708a7 100644 --- a/modelexpress_server/src/p2p/backend/kubernetes.rs +++ b/modelexpress_server/src/p2p/backend/kubernetes.rs @@ -50,8 +50,17 @@ impl KubernetesBackend { Api::namespaced(self.client.clone(), &self.namespace) } - fn pod_owner_references(pod_name: &str, pod_uid: &str) -> Option> { - if pod_name.is_empty() || pod_uid.is_empty() { + fn pod_owner_references( + pod_name: &str, + pod_uid: &str, + pod_namespace: &str, + metadata_namespace: &str, + ) -> Option> { + if pod_name.is_empty() + || pod_uid.is_empty() + || pod_namespace.is_empty() + || pod_namespace != metadata_namespace + { return None; } @@ -207,6 +216,7 @@ impl MetadataBackend for KubernetesBackend { worker: WorkerMetadata, pod_name: &str, pod_uid: &str, + pod_namespace: &str, ) -> MetadataResult<()> { let source_id = crate::p2p::source_identity::compute_mx_source_id(identity); let source_id = source_id.as_str(); @@ -219,7 +229,8 @@ impl MetadataBackend for KubernetesBackend { // First, ensure the CR exists let existing = api.get_opt(&cr_name).await?; - let pod_owner_references = Self::pod_owner_references(pod_name, pod_uid); + let pod_owner_references = + Self::pod_owner_references(pod_name, pod_uid, pod_namespace, &self.namespace); if existing.is_none() { let new_cr = ModelMetadata { @@ -845,16 +856,21 @@ mod tests { } #[test] - fn pod_owner_references_require_name_and_uid() { - assert!(KubernetesBackend::pod_owner_references("pod-0", "").is_none()); - assert!(KubernetesBackend::pod_owner_references("", "pod-uid").is_none()); + fn pod_owner_references_require_complete_same_namespace_identity() { + assert!(KubernetesBackend::pod_owner_references("pod-0", "", "ns", "ns").is_none()); + assert!(KubernetesBackend::pod_owner_references("", "pod-uid", "ns", "ns").is_none()); + assert!(KubernetesBackend::pod_owner_references("pod-0", "pod-uid", "", "ns").is_none()); + assert!( + KubernetesBackend::pod_owner_references("pod-0", "pod-uid", "other", "ns").is_none() + ); } #[test] fn pod_owner_references_identify_the_publishing_pod() { - let Some(owner_references) = KubernetesBackend::pod_owner_references("pod-0", "pod-uid") + let Some(owner_references) = + KubernetesBackend::pod_owner_references("pod-0", "pod-uid", "ns", "ns") else { - panic!("pod name and UID should produce an owner reference"); + panic!("a complete same-namespace pod identity should produce an owner reference"); }; assert_eq!(owner_references.len(), 1); diff --git a/modelexpress_server/src/p2p/backend/memory.rs b/modelexpress_server/src/p2p/backend/memory.rs index a32f9370..7e964025 100644 --- a/modelexpress_server/src/p2p/backend/memory.rs +++ b/modelexpress_server/src/p2p/backend/memory.rs @@ -61,6 +61,7 @@ impl MetadataBackend for InMemoryMetadataBackend { worker: WorkerMetadata, _pod_name: &str, _pod_uid: &str, + _pod_namespace: &str, ) -> MetadataResult<()> { let source_id = crate::p2p::source_identity::compute_mx_source_id(identity); let record = WorkerRecord::from(worker); @@ -235,7 +236,7 @@ mod tests { let source_id = compute_mx_source_id(&id); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Ready), "", "") + .publish_metadata(&id, "w1", worker(0, SourceStatus::Ready), "", "", "") .await .expect("publish"); let record = backend @@ -269,7 +270,7 @@ mod tests { for rank in [2, 0, 1] { backend - .publish_metadata(&id, "w1", worker(rank, SourceStatus::Ready), "", "") + .publish_metadata(&id, "w1", worker(rank, SourceStatus::Ready), "", "", "") .await .expect("publish"); } @@ -290,11 +291,11 @@ mod tests { let id = identity("m"); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing), "", "") + .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing), "", "", "") .await .expect("publish r0"); backend - .publish_metadata(&id, "w1", worker(1, SourceStatus::Ready), "", "") + .publish_metadata(&id, "w1", worker(1, SourceStatus::Ready), "", "", "") .await .expect("publish r1"); @@ -334,11 +335,11 @@ mod tests { let source_id = compute_mx_source_id(&id); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing), "", "") + .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing), "", "", "") .await .expect("publish r0"); backend - .publish_metadata(&id, "w1", worker(3, SourceStatus::Ready), "", "") + .publish_metadata(&id, "w1", worker(3, SourceStatus::Ready), "", "", "") .await .expect("publish r3"); @@ -363,7 +364,7 @@ mod tests { let source_id = compute_mx_source_id(&id); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing), "", "") + .publish_metadata(&id, "w1", worker(0, SourceStatus::Initializing), "", "", "") .await .expect("publish"); backend @@ -403,11 +404,11 @@ mod tests { let source_id = compute_mx_source_id(&id); backend - .publish_metadata(&id, "w1", worker(0, SourceStatus::Ready), "", "") + .publish_metadata(&id, "w1", worker(0, SourceStatus::Ready), "", "", "") .await .expect("publish w1"); backend - .publish_metadata(&id, "w2", worker(0, SourceStatus::Ready), "", "") + .publish_metadata(&id, "w2", worker(0, SourceStatus::Ready), "", "", "") .await .expect("publish w2"); diff --git a/modelexpress_server/src/p2p/backend/redis.rs b/modelexpress_server/src/p2p/backend/redis.rs index c9aee250..27cdd973 100644 --- a/modelexpress_server/src/p2p/backend/redis.rs +++ b/modelexpress_server/src/p2p/backend/redis.rs @@ -409,6 +409,7 @@ impl MetadataBackend for RedisBackend { worker: WorkerMetadata, _pod_name: &str, _pod_uid: &str, + _pod_namespace: &str, ) -> MetadataResult<()> { let source_id = crate::p2p::source_identity::compute_mx_source_id(identity); let mut conn = self.get_conn().await?; diff --git a/modelexpress_server/src/p2p/service.rs b/modelexpress_server/src/p2p/service.rs index f7c64e7b..7e5ee501 100644 --- a/modelexpress_server/src/p2p/service.rs +++ b/modelexpress_server/src/p2p/service.rs @@ -116,7 +116,14 @@ impl P2pService for P2pServiceImpl { match self .state - .publish_metadata(&identity, &worker_id, worker, &req.pod_name, &req.pod_uid) + .publish_metadata( + &identity, + &worker_id, + worker, + &req.pod_name, + &req.pod_uid, + &req.pod_namespace, + ) .await { Ok(()) => { @@ -433,11 +440,11 @@ mod tests { async fn test_publish_metadata_success() { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() - .withf(|_, _, _, pod_name, pod_uid| { - pod_name == "vllm-worker-0" && pod_uid == "pod-uid-1" + .withf(|_, _, _, pod_name, pod_uid, pod_namespace| { + pod_name == "vllm-worker-0" && pod_uid == "pod-uid-1" && pod_namespace == "default" }) .once() - .returning(|_, _, _, _, _| Ok(())); + .returning(|_, _, _, _, _, _| Ok(())); let svc = make_service(mock); let resp = svc @@ -456,6 +463,7 @@ mod tests { worker_id: "worker-uuid-1".to_string(), pod_name: "vllm-worker-0".to_string(), pod_uid: "pod-uid-1".to_string(), + pod_namespace: "default".to_string(), })) .await .expect("rpc") @@ -471,7 +479,7 @@ mod tests { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() .once() - .returning(|_, _, _, _, _| Err("storage unavailable".into())); + .returning(|_, _, _, _, _, _| Err("storage unavailable".into())); let svc = make_service(mock); let resp = svc diff --git a/modelexpress_server/src/p2p/state.rs b/modelexpress_server/src/p2p/state.rs index a2880b9a..24e87d1f 100644 --- a/modelexpress_server/src/p2p/state.rs +++ b/modelexpress_server/src/p2p/state.rs @@ -97,10 +97,18 @@ impl P2pStateManager { worker: WorkerMetadata, pod_name: &str, pod_uid: &str, + pod_namespace: &str, ) -> MetadataResult<()> { self.get_backend() .await? - .publish_metadata(identity, worker_id, worker, pod_name, pod_uid) + .publish_metadata( + identity, + worker_id, + worker, + pod_name, + pod_uid, + pod_namespace, + ) .await } @@ -459,16 +467,19 @@ mod tests { async fn test_publish_metadata_calls_backend() { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() - .withf(|identity, worker_id, worker, pod_name, pod_uid| { - identity.model_name == "my-model" - && identity.tensor_parallel_size == 8 - && worker_id == "a1b2c3d4" - && worker.worker_rank == 3 - && pod_name == "vllm-worker-0" - && pod_uid == "pod-uid-1" - }) + .withf( + |identity, worker_id, worker, pod_name, pod_uid, pod_namespace| { + identity.model_name == "my-model" + && identity.tensor_parallel_size == 8 + && worker_id == "a1b2c3d4" + && worker.worker_rank == 3 + && pod_name == "vllm-worker-0" + && pod_uid == "pod-uid-1" + && pod_namespace == "default" + }, + ) .once() - .returning(|_, _, _, _, _| Ok(())); + .returning(|_, _, _, _, _, _| Ok(())); let manager = P2pStateManager::with_backend(Arc::new(mock)); manager @@ -484,6 +495,7 @@ mod tests { }, "vllm-worker-0", "pod-uid-1", + "default", ) .await .expect("publish_metadata failed"); @@ -494,7 +506,7 @@ mod tests { let mut mock = MockMetadataBackend::new(); mock.expect_publish_metadata() .once() - .returning(|_, _, _, _, _| Err("storage unavailable".into())); + .returning(|_, _, _, _, _, _| Err("storage unavailable".into())); let manager = P2pStateManager::with_backend(Arc::new(mock)); assert!( @@ -504,7 +516,8 @@ mod tests { "a1b2c3d4", WorkerMetadata::default(), "", - "" + "", + "", ) .await .is_err()