use anyhow::Result;
use crate::{cli, scheduler, scheduling_policy};
pub(super) fn resolve_max_batch_size(
args: &cli::ServeArgs,
world_size: usize,
scheduler_model: &dyn metrale_model_engine::traits::Model,
) -> Result<usize> {
let built = args.built_max_batch_size()?;
let max_batch_size = if world_size > 1 {
if scheduler_model.ep_protocol_v2() {
tracing::info!(target: "met::main_modules::serve_load", "EP v2 active: honoring max_batch_size={}",
built,
);
built
} else {
tracing::info!(target: "met::main_modules::serve_load", "EP v1 active: forcing max_batch_size=1");
1
}
} else {
built
};
if scheduler_model.hc_mult() > 0 && max_batch_size > 1 {
tracing::info!(target: "met::main_modules::serve_load", "mHC highway model: concurrency {max_batch_size} via the per-seq \
highway decode loop (batched highway kernels are the perf \
follow-up)"
);
}
anyhow::ensure!(
max_batch_size <= metrale_gpu_runtime::buffers::DECODE_META_MAX_ROWS,
"--max-batch-size {max_batch_size} exceeds the derived decode-metadata \
ceiling of {} rows (DECODE_META_MAX_ROWS)",
metrale_gpu_runtime::buffers::DECODE_META_MAX_ROWS
);
Ok(max_batch_size)
}
pub(super) fn resolve_speculation(
args: &cli::ServeArgs,
scheduler_model: &dyn metrale_model_engine::traits::Model,
) -> (
bool,
bool,
bool,
usize,
metrale_speculative::dflash_rung::DflashRung,
) {
let use_speculative = (args.speculative || args.dflash) && scheduler_model.has_proposer();
let use_self_spec = args.self_speculative && scheduler_model.has_self_speculative();
let use_ngram_spec = args.ngram_speculative;
let num_drafts = if args.dflash {
let g = scheduler_model
.dflash_gamma()
.unwrap_or_else(|| args.serve_dflash_gamma());
g.saturating_sub(1).max(1)
} else {
args.resolved_num_drafts()
};
let dflash_rung = metrale_speculative::dflash_rung::DflashRung::new();
if args.dflash {
dflash_rung.configure(
num_drafts + 1,
args.dflash_gamma.is_some(),
metrale_model_layers::layers::qwen3_ssm::gdn_flags::gdn_woa_enabled(),
);
dflash_rung.set_ladder(args.dflash_draft_ladder_resolved.clone());
dflash_rung.set_adaptive_k(
args.dflash_args
.dflash_adaptive_k_resolved
.clone()
.map(metrale_speculative::dflash_adaptive_k::AdaptiveK::new),
);
}
if args.dflash {
tracing::info!(target: "met::main_modules::serve_load", "DFlash speculative decoding: ENABLED (γ={}, window={}, drafter installed)",
num_drafts + 1,
if args.dflash_window_size == 0 {
"full".to_string()
} else {
args.dflash_window_size.to_string()
}
);
} else if use_ngram_spec {
tracing::info!(target: "met::main_modules::serve_load", "N-gram speculative decoding: ENABLED (K=2 verify, CPU proposer)");
} else if use_self_spec {
tracing::info!(target: "met::main_modules::serve_load", "Self-speculative decoding: ENABLED ({num_drafts} drafts/step, layer-skipping)"
);
} else if use_speculative {
tracing::info!(target: "met::main_modules::serve_load", "Speculative decoding: ENABLED ({num_drafts} drafts/step)");
} else if scheduler_model.has_proposer() {
tracing::info!(target: "met::main_modules::serve_load", "MTP proposer available but speculative decoding disabled (use --speculative to enable)"
);
}
(
use_speculative,
use_self_spec,
use_ngram_spec,
num_drafts,
dflash_rung,
)
}
pub(super) fn scheduling_policy(
args: &cli::ServeArgs,
) -> Result<Box<dyn scheduling_policy::SchedulingPolicy>> {
let policy: Box<dyn scheduling_policy::SchedulingPolicy> = match args.scheduler.as_str() {
"fifo" => {
tracing::info!(target: "met::main_modules::serve_load", "Scheduling policy: FIFO");
Box::new(scheduling_policy::FifoPolicy)
}
"slai" => {
tracing::info!(target: "met::main_modules::serve_load", "Scheduling policy: SLAI (TBT deadline={}ms)",
args.tbt_deadline_ms,
);
Box::new(scheduling_policy::SlaiPolicy::new(args.tbt_deadline_ms))
}
other => anyhow::bail!("Unknown scheduler '{}'. Supported: fifo, slai", other,),
};
Ok(policy)
}
pub(super) fn scheduler_device(
args: &cli::ServeArgs,
scheduler_model: std::sync::Arc<dyn metrale_model_engine::traits::Model>,
max_batch_size: usize,
) -> Box<scheduler::io::DynDevice> {
if args.scheduler_config == "async" {
match scheduler::io::AsyncDeviceIo::new(
std::sync::Arc::clone(&scheduler_model),
max_batch_size,
) {
Ok(dev) => Box::new(dev),
Err(e) => {
tracing::warn!(target: "met::main_modules::serve_load", "--scheduler-config async: {e:#}; serving with the synchronous router"
);
Box::new(scheduler::io::SyncDeviceIo::new(scheduler_model))
}
}
} else {
Box::new(scheduler::io::SyncDeviceIo::new(scheduler_model))
}
}