Skip to content

Commit cb71be9

Browse files
ezhourealPeaBrane
andauthored
test: add tests for kv_router::scheduler (#1491)
Signed-off-by: Tianer Zhou <ezhoureal@gmail.com> Co-authored-by: Yan Ru Pei <yanrpei@gmail.com>
1 parent fc5ddd2 commit cb71be9

File tree

1 file changed

+187
-0
lines changed

1 file changed

+187
-0
lines changed

lib/llm/src/kv_router/scheduler.rs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,190 @@ impl WorkerSelector for DefaultWorkerSelector {
349349
})
350350
}
351351
}
352+
353+
#[cfg(test)]
354+
mod tests {
355+
use super::*;
356+
357+
// Helper to create a worker endpoint
358+
fn create_endpoint(
359+
worker_id: i64,
360+
gpu_cache_usage_perc: f32,
361+
num_requests_waiting: u64,
362+
) -> Endpoint {
363+
Endpoint {
364+
name: format!("worker-{}", worker_id),
365+
subject: format!("worker-subject-{:x}", worker_id),
366+
data: ForwardPassMetrics {
367+
gpu_cache_usage_perc,
368+
num_requests_waiting,
369+
// Other fields can be default initialized for this test
370+
..Default::default()
371+
},
372+
}
373+
}
374+
375+
// Helper to create ProcessedEndpoints
376+
struct WorkerInfo {
377+
id: i64,
378+
usage: f32,
379+
waiting: u64,
380+
}
381+
fn create_workers(workers: Vec<WorkerInfo>) -> ProcessedEndpoints {
382+
let mut endpoints = HashMap::new();
383+
for worker in workers {
384+
endpoints.insert(
385+
worker.id,
386+
create_endpoint(worker.id, worker.usage, worker.waiting),
387+
);
388+
}
389+
ProcessedEndpoints {
390+
endpoints,
391+
load_avg: 0.0,
392+
load_std: 0.0,
393+
}
394+
}
395+
396+
// Helper to create a scheduling request
397+
struct WorkerOverlap {
398+
worker_id: i64,
399+
overlap_blocks: u32,
400+
}
401+
fn create_request(overlaps: Vec<WorkerOverlap>, isl_tokens: usize) -> SchedulingRequest {
402+
SchedulingRequest {
403+
isl_tokens,
404+
overlap: OverlapScores {
405+
scores: overlaps
406+
.into_iter()
407+
.map(|wo| (wo.worker_id, wo.overlap_blocks))
408+
.collect(),
409+
frequencies: vec![],
410+
},
411+
resp_tx: tokio::sync::oneshot::channel().0,
412+
}
413+
}
414+
415+
#[test]
416+
fn test_select_worker_basic() {
417+
// Setup workers
418+
let workers = create_workers(vec![
419+
WorkerInfo {
420+
id: 1,
421+
usage: 0.50,
422+
waiting: 1,
423+
},
424+
WorkerInfo {
425+
id: 2,
426+
usage: 0.80,
427+
waiting: 0,
428+
},
429+
]);
430+
431+
// Setup request: 100 tokens, block_size=20 (5 blocks)
432+
let request = create_request(
433+
vec![
434+
WorkerOverlap {
435+
worker_id: 1,
436+
overlap_blocks: 3,
437+
},
438+
WorkerOverlap {
439+
worker_id: 2,
440+
overlap_blocks: 4,
441+
},
442+
],
443+
100,
444+
);
445+
let selector = DefaultWorkerSelector::new(None);
446+
let block_size = 20;
447+
448+
// Execute selection
449+
let result = selector
450+
.select_worker(&workers, &request, block_size)
451+
.expect("Should select a worker");
452+
// Worker 2 should win because:
453+
// Worker1: 2.0 * 0.600 - 1.0 * 0.500 - 1.0 * 1.000 = -0.3
454+
// Worker2: 2.0 * 0.800 - 1.0 * 0.800 - 1.0 * 0.000 = 0.8
455+
assert_eq!(result.worker_id, 2);
456+
assert_eq!(result.required_blocks, 5); // 100 tokens / 20 block_size
457+
assert_eq!(result.overlap_blocks, 4);
458+
}
459+
460+
#[test]
461+
fn test_no_endpoints() {
462+
let workers = create_workers(vec![]);
463+
let request = create_request(vec![], 100);
464+
let selector = DefaultWorkerSelector::new(None);
465+
let block_size = 20;
466+
467+
match selector.select_worker(&workers, &request, block_size) {
468+
Err(KvSchedulerError::NoEndpoints) => {} // Expected
469+
_ => panic!("Should return NoEndpoints error"),
470+
}
471+
}
472+
473+
#[test]
474+
fn test_no_overlap_scores() {
475+
// Workers exist but request has no overlap scores
476+
let workers = create_workers(vec![WorkerInfo {
477+
id: 1,
478+
usage: 0.50,
479+
waiting: 1,
480+
}]);
481+
let request = create_request(vec![], 100); // No overlaps
482+
let selector = DefaultWorkerSelector::new(None);
483+
let block_size = 20;
484+
485+
let result = selector
486+
.select_worker(&workers, &request, block_size)
487+
.expect("Should fallback to selecting worker");
488+
489+
// Worker1 should be selected with 0 overlap
490+
assert_eq!(result.worker_id, 1);
491+
assert_eq!(result.overlap_blocks, 0);
492+
}
493+
494+
#[test]
495+
fn test_custom_weights() {
496+
// Setup workers
497+
let workers = create_workers(vec![
498+
WorkerInfo {
499+
id: 1,
500+
usage: 0.50,
501+
waiting: 1,
502+
},
503+
WorkerInfo {
504+
id: 2,
505+
usage: 0.80,
506+
waiting: 0,
507+
},
508+
]);
509+
510+
// Custom config with high priority on GPU usage
511+
let config = KvRouterConfig {
512+
gpu_cache_usage_weight: 10.0, // Very high weight
513+
overlap_score_weight: 2.0, // just current defaults
514+
waiting_requests_weight: 1.0,
515+
};
516+
let selector = DefaultWorkerSelector::new(Some(config));
517+
let request = create_request(
518+
vec![
519+
WorkerOverlap {
520+
worker_id: 1,
521+
overlap_blocks: 3,
522+
},
523+
WorkerOverlap {
524+
worker_id: 2,
525+
overlap_blocks: 4,
526+
},
527+
],
528+
100,
529+
);
530+
let block_size = 20;
531+
532+
let result = selector
533+
.select_worker(&workers, &request, block_size)
534+
.expect("Should select worker");
535+
536+
assert_eq!(result.worker_id, 1);
537+
}
538+
}

0 commit comments

Comments
 (0)