use std::path::PathBuf;
use anyhow::{Context, Result};
use metrale_model_arch::weight_loader::DflashConfig;
use crate::cli;
pub(crate) fn model_default_drafter(ptx_set: &metrale_kernels::TargetPtxSet) -> Option<String> {
ptx_set.dflash.as_ref().map(|d| d.draft_model.to_string())
}
pub(super) fn dflash_drafter_dir(
args: &cli::ServeArgs,
model_default: Option<String>,
) -> Result<PathBuf> {
let drafter_id = args.draft_model.clone().or(model_default).context(
"--dflash set but no drafter HF id provided: pass --draft-model <ID> \
or use a target whose MODEL.toml has a [dflash] section",
)?;
tracing::info!("DFlash: resolving drafter '{drafter_id}'");
crate::model_resolver::resolve_model_dir(&drafter_id, args.cache_dir.as_deref())
.context("Failed to resolve DFlash drafter checkpoint")
}
pub(super) fn read_dflash_config(drafter_dir: &std::path::Path) -> Result<DflashConfig> {
let json = std::fs::read_to_string(drafter_dir.join("config.json")).with_context(|| {
format!(
"Failed to read drafter config.json at {}",
drafter_dir.display()
)
})?;
metrale_model_arch::weight_loader::dflash_loader::parse_dflash_config(&json)
}
pub(crate) fn apply_dflash_gamma(
args: &mut cli::ServeArgs,
model_default: Option<String>,
) -> Result<()> {
if !args.dflash {
return Ok(());
}
let block = match args.dflash_gamma {
Some(_) => None,
None => Some(
read_dflash_config(&dflash_drafter_dir(args, model_default.clone())?)?
.effective_block_size(),
),
};
let gamma =
metrale_model_layers::layers::qwen3_ssm::resolve_dflash_gamma(args.dflash_gamma, block);
tracing::info!(
"DFlash γ = {gamma} ({})",
if args.dflash_gamma.is_some() {
"--dflash-gamma"
} else {
"the drafter's block size + 2"
}
);
args.dflash_gamma_resolved = Some(gamma);
if let Some(spec) = &args.dflash_draft_ladder {
let ladder = metrale_speculative::dflash_ladder::DraftLadder::parse(spec)
.with_context(|| format!("--dflash-draft-ladder {spec:?}"))?;
anyhow::ensure!(
ladder.max_drafts() < gamma,
"--dflash-draft-ladder {spec:?} asks for {} drafts per step, but γ = {gamma} \
drafts at most {}",
ladder.max_drafts(),
gamma - 1
);
args.dflash_draft_ladder_resolved = Some(ladder);
}
let draft_tp = resolve_draft_tp_size(
args.dflash_args.draft_tp_size,
super::topology::serve_world_size(args),
|| read_dflash_config(&dflash_drafter_dir(args, model_default.clone())?),
)?;
args.dflash_args.draft_tp_size_resolved = Some(draft_tp);
if let Some(spec) = &args.dflash_args.dflash_adaptive_k {
let costs = metrale_speculative::dflash_adaptive_k::CostModel::parse(spec)
.with_context(|| format!("--dflash-adaptive-k {spec:?}"))?;
anyhow::ensure!(
costs.max_drafts() < gamma,
"--dflash-adaptive-k {spec:?} prices {} drafts per step, but γ = {gamma} drafts at \
most {}",
costs.max_drafts(),
gamma - 1
);
args.dflash_args.dflash_adaptive_k_resolved = Some(costs);
}
Ok(())
}
pub(crate) fn resolve_draft_tp_size(
flag: Option<cli::serve_args_dflash::DraftTpSize>,
world: usize,
config: impl FnOnce() -> Result<DflashConfig>,
) -> Result<usize> {
let Some(flag) = flag else {
return Ok(1);
};
let n = flag.ranks(world);
anyhow::ensure!(
n == 1 || n == world,
"--draft-tp-size {n}: the drafter's collectives span the whole communicator, so it \
must be 1 or the world size {world}"
);
if n > 1 {
let c = config()?;
for rank in 0..n {
metrale_model_arch::dflash_head::tp::plan(
c.num_attention_heads,
c.num_key_value_heads,
c.intermediate_size,
c.vocab_size,
n,
rank,
)
.with_context(|| format!("--draft-tp-size {n} cannot split this drafter"))?;
}
}
Ok(n)
}
#[cfg(test)]
#[path = "dflash_gamma_tests.rs"]
mod dflash_gamma_tests;