Download crates/ds4-cli/src/lib.rs from staccs/ds4-winhip: direct link, hf CLI and curl.
- Browser
- Download file 97.8 kB
-
https://huggingface.co/staccs/ds4-winhip/resolve/main/crates/ds4-cli/src/lib.rs
- Command line
-
hf download hf://staccs/ds4-winhip/crates/ds4-cli/src/lib.rs
-
curl -L -o lib.rs https://huggingface.co/staccs/ds4-winhip/resolve/main/crates/ds4-cli/src/lib.rs
97.8 kB
//! Shadow CLI host. Calls the same C inference core through `ds4-core`.
pub mod agent;
pub mod bench;
mod conversation;
pub mod repl;
pub mod session_exec;
pub mod session_snapshot;
mod token_printer;
pub mod worker_hello;
pub mod worker_run;
use token_printer::TokenPrinter;
use ds4_core::{Backend, ModelOpenOption};
pub(crate) fn distributed_config(opt: &ds4_dist::Options) -> Option<ds4_core::DistributedConfig> {
let role = match opt.role {
ds4_dist::Role::None => return None,
ds4_dist::Role::Coordinator => ds4_core::DistributedRole::Coordinator,
ds4_dist::Role::Worker => ds4_core::DistributedRole::Worker,
};
Some(ds4_core::DistributedConfig {
role,
layer_start: opt.layers.start,
layer_end: opt.layers.end,
has_output: opt.layers.has_output,
listen_host: opt.listen_host.clone(),
listen_port: opt.listen_port,
coordinator_host: opt.coordinator_host.clone(),
coordinator_port: opt.coordinator_port,
prefill_chunk: opt.prefill_chunk,
prefill_window: opt.prefill_window,
activation_bits: opt.activation_bits,
replay_check: opt.replay_check,
debug: opt.debug,
})
}
#[derive(Debug)]
pub struct ShadowArgs {
pub model: Option<String>,
pub mtp: Option<String>,
pub mtp_draft: i32,
pub mtp_margin: f32,
pub dspark: Option<String>,
pub gen_ids: Option<String>,
pub engram_dir: Option<String>,
pub zchain: Option<String>,
pub posttrain: Option<String>,
/// The `--vision-gguf` sidecar (the ds41 tower's weights); also the
/// server's spelling, one native open slot for every family.
pub vision: Option<String>,
/// `--image` (repeatable, the engine's parser shape); only the golden
/// gate consumes it until the one-shot image request is ported.
pub images: Vec<String>,
/// `--vision-golden <VGLD>`: run the tower gate instead of generating.
pub vision_golden: Option<String>,
pub mem_budget_mb: Option<i32>,
pub no_engram: bool,
pub no_dspark: bool,
pub dspark_verify: i32,
pub no_graph: bool,
pub emit_trace: bool,
pub v41_prof: bool,
pub backend: Backend,
pub ctx: i32,
pub ctx_set: bool,
pub threads: i32,
pub tokens: Vec<i32>,
pub predict: i32,
pub n_predict: i32,
pub system: String,
pub nothink: bool,
pub prompt: Option<String>,
pub prompt_file: Option<String>,
pub dump_logprobs: Option<String>,
pub dump_logits: Option<String>,
pub dump_tokens: bool,
pub logprobs_top_k: i32,
pub temp: f32,
pub top_p: f32,
pub min_p: f32,
pub min_p_set: bool,
pub seed: u64,
pub dry_multiplier: f32,
pub dry_base: f32,
pub dry_allowed_length: i32,
pub lifecycle_only: bool,
pub identify: bool,
pub inventory: bool,
pub tokenize: bool,
pub tok_family: Option<String>,
pub tok_cmd: Option<String>,
pub tok_arg: Option<String>,
pub session_plan: bool,
pub session_cmd: Option<String>,
pub session_args: Vec<String>,
pub session_payload: bool,
pub payload_cmd: Option<String>,
pub payload_args: Vec<String>,
pub bind_names: bool,
pub bind_names_variant: Option<String>,
pub bind_plan: bool,
pub validate: bool,
pub layout: bool,
pub layout_variant: Option<String>,
ssd_options: Vec<ds4_core::ModelOpenOption>,
pub dist: ds4_dist::Options,
pub help: bool,
}
impl Default for ShadowArgs {
fn default() -> Self {
Self {
model: Some("ds4flash.gguf".into()),
mtp: None,
mtp_draft: 1,
mtp_margin: 3.0,
dspark: None,
gen_ids: None,
engram_dir: None,
zchain: None,
posttrain: None,
vision: None,
images: Vec::new(),
vision_golden: None,
mem_budget_mb: None,
no_engram: false,
no_dspark: false,
dspark_verify: 0,
no_graph: false,
emit_trace: false,
v41_prof: false,
backend: default_backend(),
ctx: 32768,
ctx_set: false,
threads: 0,
tokens: Vec::new(),
predict: 0,
n_predict: 50000,
system: "You are a helpful assistant".into(),
nothink: false,
prompt: None,
prompt_file: None,
dump_logprobs: None,
dump_logits: None,
dump_tokens: false,
logprobs_top_k: 20,
temp: 1.0,
top_p: 1.0,
min_p: 0.05,
min_p_set: false,
seed: 0,
dry_multiplier: 0.0,
dry_base: 1.75,
dry_allowed_length: 2,
lifecycle_only: false,
identify: false,
inventory: false,
tokenize: false,
tok_family: None,
tok_cmd: None,
tok_arg: None,
session_plan: false,
session_cmd: None,
session_args: Vec::new(),
session_payload: false,
payload_cmd: None,
payload_args: Vec::new(),
bind_names: false,
bind_names_variant: None,
bind_plan: false,
validate: false,
layout: false,
layout_variant: None,
ssd_options: Vec::new(),
dist: ds4_dist::Options::default(),
help: false,
}
}
}
pub fn parse_args(args: impl IntoIterator<Item = String>) -> Result<ShadowArgs, String> {
let mut parsed = ShadowArgs::default();
let mut iter = args.into_iter().peekable();
let _argv0 = iter.next();
while let Some(arg) = iter.next() {
match arg.as_str() {
"-h" | "--help" => parsed.help = true,
"-m" | "--model" => {
parsed.model = Some(require_value(&arg, iter.next())?);
}
"--mtp" => {
parsed.mtp = Some(require_value(&arg, iter.next())?);
}
"--mtp-draft" => {
parsed.mtp_draft = parse_positive_i32(&arg, &require_value(&arg, iter.next())?)?;
}
"--mtp-margin" => {
parsed.mtp_margin =
parse_f32_range(&arg, &require_value(&arg, iter.next())?, 0.0, 1000.0)?;
}
"--ssd-streaming" => parsed
.ssd_options
.push(ds4_core::ModelOpenOption::SsdStreaming),
"--ssd-streaming-cold" => parsed
.ssd_options
.push(ds4_core::ModelOpenOption::SsdStreamingCold),
"--ssd-streaming-cache-experts" => {
let value = require_value(&arg, iter.next())?;
parsed.ssd_options.push(
ds4_core::ModelOpenOption::ssd_cache(&value).map_err(|error| error.message)?,
);
}
"--dspark" => {
parsed.dspark = Some(require_value(&arg, iter.next())?);
}
/* V4.1 (ds41) run flags: the engine's own spellings (cli_opts.c).
* --gen-ids is the family's run input -- its chat rendering is not
* ported yet, so text prompts are refused by name on that path.
* The engine's bare --dspark (mode 2) collides with this CLI's
* V4-era --dspark <gguf> sidecar flag; mode 2 == mode 1 for a
* greedy-only engine, so --no-dspark and the default are the two
* reachable modes here. */
"--gen-ids" => {
parsed.gen_ids = Some(require_value(&arg, iter.next())?);
}
"--engram-dir" => {
parsed.engram_dir = Some(require_value(&arg, iter.next())?);
}
/* Unit Z: the deployment's sidecar spelling (install.sh boots
* with --zchain <domain dir> [--posttrain <dir>]). */
"--zchain" => {
parsed.zchain = Some(require_value(&arg, iter.next())?);
}
"--posttrain" => {
parsed.posttrain = Some(require_value(&arg, iter.next())?);
}
/* The ds41 vision tower (cli_opts.c:352-354): --vision-gguf is the
* reference install.sh name for the sidecar, --image is the gate's
* picture, --vision-golden its fixture. */
"--vision" | "--vision-gguf" => {
parsed.vision = Some(require_value(&arg, iter.next())?);
}
"--image" => {
parsed.images.push(require_value(&arg, iter.next())?);
}
"--vision-golden" => {
parsed.vision_golden = Some(require_value(&arg, iter.next())?);
}
/* Unit Z: the engine's process red line (core_util.c:198-200);
* install.sh boots the server with 110000. */
"--mem-budget-mb" => {
let v = require_value(&arg, iter.next())?;
parsed.mem_budget_mb = Some(parse_i32(&arg, &v)?);
}
"--v41-no-engram" => parsed.no_engram = true,
"--no-dspark" => parsed.no_dspark = true,
"--dspark-verify" => {
parsed.dspark_verify = parse_i32(&arg, &require_value(&arg, iter.next())?)?;
}
"--no-graph" => parsed.no_graph = true,
"--emit-trace" => parsed.emit_trace = true,
"--v41-prof" => parsed.v41_prof = true,
"--backend" => {
parsed.backend = parse_backend(&require_value(&arg, iter.next())?)?;
}
/* C CLI backend spellings, so the proof harness command line
* (`--cuda -m ...`) drives this shadow unchanged. */
"--cuda" => parsed.backend = Backend::Cuda,
"--cpu" => parsed.backend = Backend::Cpu,
"--metal" => parsed.backend = Backend::Metal,
"-c" | "--ctx" => {
parsed.ctx = parse_positive_i32(&arg, &require_value(&arg, iter.next())?)?;
parsed.ctx_set = true;
}
"-n" | "--tokens" | "--n-predict" => {
let value = require_value(&arg, iter.next())?;
if arg == "--tokens" && value.contains(',') {
return Err(
"--tokens is the output budget; use --token-ids for raw token IDs".into(),
);
}
parsed.n_predict = parse_positive_i32(&arg, &value)?;
}
"--temp" => {
let v = require_value(&arg, iter.next())?;
parsed.temp = parse_f32_range(&arg, &v, 0.0, 100.0)?;
}
"--top-p" => {
let v = require_value(&arg, iter.next())?;
parsed.top_p = parse_f32_range(&arg, &v, 0.0, 1.0)?;
}
"--min-p" => {
let v = require_value(&arg, iter.next())?;
parsed.min_p = parse_f32_range(&arg, &v, 0.0, 1.0)?;
parsed.min_p_set = true;
}
"--seed" => {
let v = require_value(&arg, iter.next())?;
parsed.seed = parse_positive_u64(&arg, &v)?;
}
"--dry-multiplier" => {
let v = require_value(&arg, iter.next())?;
parsed.dry_multiplier = parse_f32_range(&arg, &v, 0.0, 100.0)?;
}
"--dry-base" => {
let v = require_value(&arg, iter.next())?;
parsed.dry_base = parse_f32_range(&arg, &v, 0.0, 100.0)?;
}
"--dry-allowed-length" => {
let v = require_value(&arg, iter.next())?;
parsed.dry_allowed_length = parse_positive_i32(&arg, &v)?;
}
"--think" => parsed.nothink = false,
"--nothink" => parsed.nothink = true,
"-sys" | "--system" => {
parsed.system = require_value(&arg, iter.next())?;
}
"-p" | "--prompt" => {
if parsed.prompt.is_some() || parsed.prompt_file.is_some() {
return Err("specify only one prompt source".into());
}
parsed.prompt = Some(require_value(&arg, iter.next())?);
}
"--prompt-file" => {
if parsed.prompt.is_some() || parsed.prompt_file.is_some() {
return Err("specify only one prompt source".into());
}
parsed.prompt_file = Some(require_value(&arg, iter.next())?);
}
"--dump-tokens" => parsed.dump_tokens = true,
"--dump-logits" => {
parsed.dump_logits = Some(
iter.next()
.ok_or_else(|| "ds4: missing value for --dump-logits".to_string())?,
);
}
"--dump-logprobs" => {
parsed.dump_logprobs = Some(require_value(&arg, iter.next())?);
}
"--logprobs-top-k" => {
parsed.logprobs_top_k = parse_i32(&arg, &require_value(&arg, iter.next())?)?;
}
"-t" | "--threads" => {
parsed.threads = parse_i32(&arg, &require_value(&arg, iter.next())?)?;
}
"--token-ids" => {
parsed.tokens = parse_tokens(&require_value(&arg, iter.next())?)?;
}
"--predict" => {
parsed.predict = parse_i32(&arg, &require_value(&arg, iter.next())?)?;
}
"--lifecycle" => parsed.lifecycle_only = true,
"--identify" => parsed.identify = true,
"--inventory" => parsed.inventory = true,
"--tokenize" => {
parsed.tokenize = true;
parsed.tok_family = Some(require_value(&arg, iter.next())?);
parsed.tok_cmd = Some(require_value(&arg, iter.next())?);
if let Some(peek) = iter.peek() {
if !peek.starts_with('-') {
parsed.tok_arg = iter.next();
}
}
}
"--session-plan" => {
parsed.session_plan = true;
parsed.session_cmd = Some(require_value(&arg, iter.next())?);
while let Some(peek) = iter.peek() {
if peek.starts_with('-') {
break;
}
parsed.session_args.push(iter.next().unwrap());
}
}
"--session-payload" => {
parsed.session_payload = true;
parsed.payload_cmd = Some(require_value(&arg, iter.next())?);
while let Some(peek) = iter.peek() {
if peek.starts_with('-') {
break;
}
parsed.payload_args.push(iter.next().unwrap());
}
}
"--bind-names" => {
parsed.bind_names = true;
parsed.bind_names_variant = Some(require_value(&arg, iter.next())?);
}
"--bind-plan" => parsed.bind_plan = true,
"--validate" => parsed.validate = true,
"--layout" => {
parsed.layout = true;
parsed.layout_variant = Some(require_value(&arg, iter.next())?);
}
other => match ds4_dist::parse_cli_arg(other, &mut iter, &mut parsed.dist)? {
ds4_dist::CliResult::Matched => {}
ds4_dist::CliResult::NotMatched => {
return Err(format!("unknown argument: {other}"));
}
ds4_dist::CliResult::Error => unreachable!(),
},
}
}
ds4_dist::prepare_engine_options(&parsed.dist)?;
Ok(parsed)
}
const DEEPSEEK_BOS: &str = "<|begin▁of▁sentence|>";
fn is_rendered_chat_prompt(prompt: &str) -> bool {
prompt.starts_with(DEEPSEEK_BOS)
}
fn prompt_text(args: &ShadowArgs) -> Result<String, String> {
match (&args.prompt, &args.prompt_file) {
(Some(prompt), None) => Ok(prompt.clone()),
(None, Some(path)) => {
std::fs::read_to_string(path).map_err(|e| format!("prompt-file: {e}"))
}
(Some(_), Some(_)) => Err("specify only one prompt source".into()),
(None, None) => Err("one-shot generation requires -p or --prompt-file".into()),
}
}
fn validate_one_shot_args(args: &ShadowArgs) -> Result<(), String> {
if args.prompt.is_none() && args.prompt_file.is_none() {
return Err("one-shot generation requires -p or --prompt-file".into());
}
Ok(())
}
fn use_mtp_spec(temp: f32, mtp: Option<&str>, draft: i32) -> bool {
temp <= 0.0 && mtp.is_some() && draft > 1 && std::env::var_os("DS4_MTP_SPEC_DISABLE").is_none()
}
fn use_iquest_spec(temp: f32, family: ds4_core::ModelFamily, draft: i32) -> bool {
family == ds4_core::ModelFamily::IQuestQ1
&& temp <= 0.0
&& (2..=ds4_core::IQuestPlan::MAX_MTP_DRAFT as i32).contains(&draft)
&& std::env::var_os("DS4_MTP_SPEC_DISABLE").is_none()
}
fn use_glm_spec(temp: f32, family: ds4_core::ModelFamily, draft: i32) -> bool {
matches!(
family,
ds4_core::ModelFamily::Glm53
| ds4_core::ModelFamily::Glm53Vq
| ds4_core::ModelFamily::Ling31Vq
) && temp <= 0.0
&& draft > 1
&& ds4_core::check_mtp_draft(family, draft).is_ok()
&& std::env::var_os("DS4_MTP_SPEC_DISABLE").is_none()
}
fn use_naive_spec(temp: f32, dspark: Option<&str>, draft: i32) -> bool {
temp <= 0.0
&& dspark.is_some()
&& draft >= 1
&& std::env::var_os("DS4_MTP_SPEC_DISABLE").is_none()
}
fn mtp_open_options(
args: &ShadowArgs,
family: Option<ds4_core::ModelFamily>,
) -> Result<Vec<ModelOpenOption>, String> {
if let Some(family) = family {
ds4_core::check_mtp_draft(family, args.mtp_draft).map_err(|error| error.message)?;
}
let mut options = Vec::new();
options.extend(args.ssd_options.iter().cloned());
if args.ssd_options.contains(&ModelOpenOption::SsdStreaming) {
options.push(ModelOpenOption::ServingBudget(ds4_core::ServingRequest {
ctx: args.ctx,
backend: args.backend,
max_seqs: ds4_core::MaxSeqs::Off,
..ds4_core::ServingRequest::default()
}));
}
if args.mtp.is_some()
|| args.dspark.is_some()
|| std::env::var_os("DS4_DSPARK_MODEL").is_some()
|| (matches!(
family,
Some(
ds4_core::ModelFamily::IQuestQ1
| ds4_core::ModelFamily::Glm53
| ds4_core::ModelFamily::Glm53Vq
| ds4_core::ModelFamily::Ling31Vq
)
) && args.mtp_draft > 1)
{
options.push(ModelOpenOption::MtpDraftTokens(args.mtp_draft));
options.push(ModelOpenOption::MtpMargin(args.mtp_margin));
}
Ok(options)
}
/// -c, else the family's native context default (GLM VQ), else the host default.
fn session_ctx(args: &ShadowArgs, caps: ds4_core::ServingCaps) -> i32 {
if args.ctx_set {
return args.ctx;
}
ds4_core::native_ctx_default(caps).map_or(args.ctx, |ctx| ctx as i32)
}
fn generation_limit(ctx: i32, pos: i32, requested: i32) -> i32 {
let room = ctx.saturating_sub(pos);
requested.min(room).max(0)
}
fn sampled_generation_limit(ctx: i32, pos: i32, requested: i32) -> i32 {
let room = ctx.saturating_sub(pos).saturating_sub(1);
requested.min(room).max(0)
}
fn session_ready<'m>(
model: &'m ds4_core::Model,
args: &ShadowArgs,
ctx: i32,
) -> Result<ds4_core::Session<'m>, String> {
let session = model.session(ctx).map_err(|e| e.to_string())?;
if args.dist.role != ds4_dist::Role::Coordinator {
return Ok(session);
}
let mut ticks = 0u32;
loop {
if session
.distributed_route_ready()
.map_err(|e| e.to_string())?
{
if ticks != 0 {
eprintln!("ds4-rs: distributed route ready");
}
return Ok(session);
}
if ticks % 4 == 0 {
eprintln!("ds4-rs: waiting for distributed route");
}
ticks = ticks.wrapping_add(1);
std::thread::sleep(std::time::Duration::from_millis(250));
}
}
fn apply_effort_prefix(
vocab: &ds4_core::Vocab,
chat: &mut repl::ReplChat,
session: Option<&mut ds4_core::Session<'_>>,
) {
if chat.conversation.is_some() {
return;
}
let want = chat.wants_effort_prefix();
if want && chat.prefix_tokens == 0 {
let mut prefix = ds4_core::TokenBuffer::new();
vocab.chat_append_effort_prefix(&mut prefix, chat.effective_think());
chat.transcript.insert(1, prefix.as_slice());
chat.prefix_tokens = prefix.len();
if let Some(session) = session {
session.invalidate();
}
} else if !want && chat.prefix_tokens > 0 {
chat.transcript.remove(1, chat.prefix_tokens);
chat.prefix_tokens = 0;
if let Some(session) = session {
session.invalidate();
}
}
}
fn run_chat_turn(
model: &ds4_core::Model,
args: &ShadowArgs,
chat: &mut repl::ReplChat,
session: &mut ds4_core::Session<'_>,
user_text: &str,
) -> Result<(), String> {
use std::io::Write;
let vocab = model.vocab();
apply_effort_prefix(vocab, chat, Some(session));
let rollback = chat.transcript.as_slice().to_vec();
let think = chat.effective_think();
let messages = chat
.conversation
.as_ref()
.map(|chat| chat.prompt(user_text));
if let Some(messages) = &messages {
let tokens = model
.encode_messages(messages, think)
.map_err(|e| e.to_string())?;
// Templates may remove old reasoning or change earlier prefixes.
if !tokens.as_slice().starts_with(&rollback) {
session.invalidate();
}
chat.transcript = tokens;
} else {
vocab
.chat_append_message(&mut chat.transcript, "user", user_text.as_bytes())
.map_err(|e| e.to_string())?;
vocab
.chat_append_assistant_prefix(&mut chat.transcript, think)
.map_err(|e| e.to_string())?;
}
if let Err(e) = session.sync(&chat.transcript) {
chat.transcript = ds4_core::TokenBuffer::from_tokens(rollback);
session.invalidate();
eprintln!("ds4: prompt processing failed: {e}");
return Ok(());
}
let max_tokens = sampled_generation_limit(session.ctx(), session.pos(), args.n_predict);
let mut rng = args.seed;
if rng == 0 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
rng = (now ^ (u64::from(std::process::id()) << 32)) | 1;
}
let stdout = std::io::stdout();
let mut out = stdout.lock();
let mut printer = TokenPrinter::new(model.family(), chat.thinking_enabled());
let use_mtp = use_mtp_spec(args.temp, args.mtp.as_deref(), args.mtp_draft)
|| use_iquest_spec(args.temp, model.family(), args.mtp_draft)
|| use_glm_spec(args.temp, model.family(), args.mtp_draft)
|| (model.family() == ds4_core::ModelFamily::NaiveN05
&& use_naive_spec(args.temp, model.dspark().map(|d| d.path()), args.mtp_draft));
let eos = model.token_eos();
let mut generated = 0i32;
let mut raw = Vec::new();
let mut failed = false;
repl::interrupt_clear();
while generated < max_tokens && !repl::interrupt_requested() {
let token = session.sample(args.temp, 0, args.top_p, args.min_p, &mut rng);
if token < 0 {
eprintln!("ds4: decode failed: failed to sample the next token");
failed = true;
break;
}
if model.token_is_stop(token) {
break;
}
let accepted = if use_mtp {
match session.eval_speculative_argmax(token, max_tokens - generated, eos) {
Ok(v) => v,
Err(e) => {
eprintln!("ds4: decode failed: {e}");
failed = true;
break;
}
}
} else if let Err(e) = session.eval(token) {
eprintln!("ds4: decode failed: {e}");
failed = true;
break;
} else {
vec![token]
};
let mut stop = false;
for t in accepted {
if model.token_is_stop(t) {
stop = true;
break;
}
let piece = model.token_text(t).map_err(|e| e.to_string())?;
raw.extend_from_slice(&piece);
printer
.write_token(&mut out, t, &piece)
.map_err(|e| e.to_string())?;
out.flush().map_err(|e| e.to_string())?;
chat.transcript.push(t);
generated += 1;
if generated >= max_tokens {
break;
}
}
if stop {
break;
}
}
printer.finish(&mut out).map_err(|e| e.to_string())?;
let interrupted = repl::interrupt_requested();
let end = if failed {
repl::InterruptEnd::Rollback
} else {
repl::interrupt_end(interrupted, generated)
};
match end {
repl::InterruptEnd::Rollback => {
chat.transcript = ds4_core::TokenBuffer::from_tokens(rollback);
session.invalidate();
}
repl::InterruptEnd::KeepEos => {
if let Some((conversation, messages)) = chat.conversation.as_mut().zip(messages) {
if let Err(error) = conversation.accept(messages, model.model_id(), &raw, think) {
chat.transcript = ds4_core::TokenBuffer::from_tokens(rollback);
session.invalidate();
eprintln!("ds4: {error}");
return Ok(());
}
}
chat.transcript.push(eos);
}
}
if interrupted {
repl::interrupt_clear();
}
Ok(())
}
/// The engine's `--vision-golden` gate (cli_main.c:193-195): one `--image`
/// and the VGLD fixture through the model's own tower
/// (core_v41_vision_gate.c:44-94, ported in ds4_core::v41_golden). Exit 0
/// when every gating check passes, 1 on a failed verdict; a missing tower or
/// an unreadable fixture is an error by name, the engine's early returns.
fn run_v41_vision_golden(
model: &ds4_core::Model,
args: &ShadowArgs,
golden: &str,
) -> Result<i32, String> {
if args.images.len() != 1 {
return Err("ds4-rs: --vision-golden takes exactly one --image".into());
}
let vision_path = args.vision.as_deref().ok_or_else(|| {
"ds4-rs: --vision-golden needs --vision-gguf <sidecar> (the tower weights)".to_string()
})?;
let meta = ds4_core::v41_media::V41VisionMeta::read(vision_path).map_err(|e| e.to_string())?;
let ok = model
.v41_vision_golden(
&meta,
std::path::Path::new(&args.images[0]),
std::path::Path::new(golden),
)
.map_err(|e| e.to_string())?;
Ok(if ok { 0 } else { 1 })
}
/// The V4.1 route's `--dump-logprobs` record: the selected ids, no logits.
///
/// The generic dump carries a per-step top-k list (run_logprob_dump reads
/// `session.top_logprobs`); the V4.1 bridge hands this route only the
/// selected id (v41_run.rs's emit callback), so the record keeps the same
/// envelope and the same `"selected":{"id":N}` field -- the field the proof
/// harness hashes into selected_token_ids_md5 -- with `top_k` 0 and an empty
/// list, and never claims a logit it does not have.
struct V41Dump {
fp: std::io::BufWriter<std::fs::File>,
step: i32,
}
impl V41Dump {
fn open(path: &str, prompt_tokens: i32) -> Result<Self, String> {
let fp = std::io::BufWriter::new(
std::fs::File::create(path).map_err(|e| format!("dump-logprobs {path}: {e}"))?,
);
let mut d = Self { fp, step: 0 };
d.w(&format!(
"{{\n \"source\":\"ds4-v41\",\n \"prompt_tokens\":{prompt_tokens},\n \"top_k\":0,\n \"steps\":[\n"
))?;
Ok(d)
}
fn w(&mut self, s: &str) -> Result<(), String> {
use std::io::Write;
self.fp.write_all(s.as_bytes()).map_err(|e| e.to_string())
}
/// Append one step. Takes the already-rendered token JSON so the writer
/// is testable without a model (json_token needs one).
fn push(&mut self, token_json: &str) -> Result<(), String> {
if self.step > 0 {
self.w(",\n")?;
}
let entry = format!(
" {{\"step\":{},\"selected\":{},\"top_logprobs\":[]}}",
self.step, token_json
);
self.w(&entry)?;
self.step += 1;
Ok(())
}
fn close(mut self) -> Result<(), String> {
use std::io::Write;
self.w("\n ]\n}\n")?;
self.fp.flush().map_err(|e| e.to_string())
}
}
/// V4.1 (ds41) one-shot: the engine's `--gen-ids` run through
/// `ds4_bridge_v41_generate` (the same one-shot entry the engine's server
/// calls, `server_generate_v41.c:414`), with the host engram feed.
fn run_v41_one_shot(
model: &ds4_core::Model,
args: &ShadowArgs,
model_path: &str,
) -> Result<i32, String> {
use std::io::Write;
let ids_path = args.gen_ids.as_deref().ok_or_else(|| {
"ds4-rs: the V4.1 family runs on --gen-ids <file> (raw token ids; its chat rendering is not ported yet)"
.to_string()
})?;
let prompt = read_ids_file(ids_path)?;
// The engine CLI's sampling face (cli_diag.c:44-50): --temp / --top-p /
// --min-p / --seed from the flags (defaults 1.0 / 1.0 / 0.05 / 0), the
// DRY trio (freq / presence have no CLI flags in the engine, they stay 0).
let mut opts = ds4_core::V41RunOptions {
engram_dir: args.engram_dir.as_deref(),
no_engram: args.no_engram,
images: None,
dspark: Some(if args.no_dspark { 0 } else { 1 }),
graph: Some(!args.no_graph),
verify_k: args.dspark_verify,
emit_trace: args.emit_trace,
prof: args.v41_prof,
/* The V4.1 KV prefix reuse is on by default inside the native run
* (U-B; engine core_v41_kvreuse.c); the CLI has no --no-kv-reuse
* switch (the server has one), so the option keeps its default. */
kv_reuse: None,
/* The disk half (item 4) is the server's (--kv-disk-dir opens the
* store there); the CLI has no store and leaves it off. */
kv_disk: None,
sampling: Some(ds4_core::V41Sampling {
temperature: args.temp,
top_p: args.top_p,
// The engine CLI's min_p default is 0.0 (DS4_DEFAULT_MIN_P,
// ds4.h:56); the port CLI's shared default is 0.05 for the other
// families, so the v41 path takes 0.0 unless --min-p was given.
min_p: if args.min_p_set { args.min_p } else { 0.0 },
top_k: 0,
seed: args.seed,
freq_penalty: 0.0,
presence_penalty: 0.0,
dry_multiplier: args.dry_multiplier,
dry_base: args.dry_base,
dry_allowed_length: args.dry_allowed_length,
}),
greedy: None, /* the engine CLI has no tool-syntax hook (cli_diag.c:50 has none) */
zchain: args.zchain.as_deref(),
posttrain: args.posttrain.as_deref(),
};
let stdout = std::io::stdout();
let mut out = stdout.lock();
let mut printer = TokenPrinter::new(model.family(), false);
let mut dump = match args.dump_logprobs.as_deref() {
Some(path) => Some(V41Dump::open(path, prompt.len() as i32)?),
None => None,
};
let mut write_err: Option<String> = None;
let mut emit = |token: i32| -> bool {
/* The stop token is recorded before the run is cut, exactly as the
* generic dump does, so the id sequence is the whole sampled one. */
if let Some(d) = dump.as_mut() {
if let Err(e) = d.push(&json_token(model, token)) {
write_err = Some(e);
return false;
}
}
if model.token_is_stop(token) {
return false;
}
let piece = match model.token_text(token) {
Ok(piece) => piece,
Err(e) => {
write_err = Some(e.to_string());
return false;
}
};
if let Err(e) = printer
.write_token(&mut out, token, &piece)
.and_then(|_| out.flush())
{
write_err = Some(e.to_string());
return false;
}
true
};
let mut progress = |_event: &str, _current: i32, _total: i32| true;
let rc = model.v41_generate(
std::path::Path::new(model_path),
&prompt,
args.n_predict,
&mut opts,
&mut emit,
&mut progress,
);
if let Some(e) = write_err {
return Err(e);
}
if let Some(d) = dump {
d.close()?;
}
rc.map_err(|e| e.to_string())?;
Ok(0)
}
fn read_ids_file(path: &str) -> Result<Vec<i32>, String> {
let text = std::fs::read_to_string(path).map_err(|e| format!("ds4-rs: {path}: {e}"))?;
let ids: Vec<i32> = text
.split_whitespace()
.map(|v| {
v.parse::<i32>()
.map_err(|_| format!("ds4-rs: {path}: not an integer: {v}"))
})
.collect::<Result<_, _>>()?;
if ids.is_empty() {
return Err(format!("ds4-rs: {path}: no token ids"));
}
Ok(ids)
}
fn run_one_shot(model: &ds4_core::Model, args: &ShadowArgs, text: &str) -> Result<i32, String> {
use std::io::Write;
const THINK_NONE: i32 = 0;
const THINK_LOW: i32 = 1;
let prompt = if is_rendered_chat_prompt(&text) {
model.tokenize_rendered_chat(&text)
} else {
model.encode_chat_prompt(
Some(args.system.as_str()),
&text,
if args.nothink { THINK_NONE } else { THINK_LOW },
)
}
.map_err(|e| e.to_string())?;
let mut session = session_ready(model, args, args.ctx)?;
session.sync(&prompt).map_err(|e| e.to_string())?;
let sampled = args.temp > 0.0;
let max_tokens = if sampled {
sampled_generation_limit(session.ctx(), session.pos(), args.n_predict)
} else {
generation_limit(session.ctx(), session.pos(), args.n_predict)
};
let mut rng = args.seed;
if sampled && rng == 0 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
rng = (now ^ (u64::from(std::process::id()) << 32)) | 1;
}
let stdout = std::io::stdout();
let mut out = stdout.lock();
let mut printer = TokenPrinter::new(model.family(), !args.nothink);
let mut decode_error = None;
let use_mtp = use_mtp_spec(args.temp, args.mtp.as_deref(), args.mtp_draft)
|| use_iquest_spec(args.temp, model.family(), args.mtp_draft)
|| use_glm_spec(args.temp, model.family(), args.mtp_draft)
|| (model.family() == ds4_core::ModelFamily::NaiveN05
&& use_naive_spec(args.temp, model.dspark().map(|d| d.path()), args.mtp_draft));
let eos = model.token_eos();
let mut generated = 0;
while generated < max_tokens {
let token = if sampled {
session.sample(args.temp, 0, args.top_p, args.min_p, &mut rng)
} else {
session.argmax()
};
if token < 0 {
decode_error = Some(if sampled {
"failed to sample the next token".into()
} else {
"failed to select the next token".into()
});
break;
}
if model.token_is_stop(token) {
break;
}
if use_mtp {
let accepted = match session.eval_speculative_argmax(token, max_tokens - generated, eos)
{
Ok(v) => v,
Err(e) => {
decode_error = Some(e.to_string());
break;
}
};
let mut stop = false;
for t in accepted {
if model.token_is_stop(t) {
stop = true;
break;
}
let piece = model.token_text(t).map_err(|e| e.to_string())?;
printer
.write_token(&mut out, t, &piece)
.map_err(|e| e.to_string())?;
out.flush().map_err(|e| e.to_string())?;
generated += 1;
if generated >= max_tokens {
break;
}
}
if stop {
break;
}
continue;
}
if sampled {
if let Err(e) = session.eval(token) {
decode_error = Some(e.to_string());
break;
}
}
let piece = model.token_text(token).map_err(|e| e.to_string())?;
printer
.write_token(&mut out, token, &piece)
.map_err(|e| e.to_string())?;
out.flush().map_err(|e| e.to_string())?;
generated += 1;
if !sampled {
if generated >= max_tokens {
break;
}
if let Err(e) = session.eval(token) {
decode_error = Some(e.to_string());
break;
}
}
}
printer.finish(&mut out).map_err(|e| e.to_string())?;
match decode_error {
Some(error) => Err(error),
None => Ok(0),
}
}
/// Proof-harness dump: mirror of the C CLI `run_logprob_dump` loop
/// (top_logprobs -> argmax -> write step -> stop check -> eval). Prompt
/// rendering follows the C CLI; token text and the stop set use the host vocab.
fn run_logprob_dump(
model: &ds4_core::Model,
args: &ShadowArgs,
prompt_text: &str,
) -> Result<i32, String> {
use std::io::Write;
const THINK_NONE: i32 = 0;
const THINK_LOW: i32 = 1;
const TOP_K_CAP: usize = 128;
let think = if args.nothink { THINK_NONE } else { THINK_LOW };
let prompt = if is_rendered_chat_prompt(&prompt_text) {
model.tokenize_rendered_chat(&prompt_text)
} else {
model.encode_chat_prompt(Some(args.system.as_str()), &prompt_text, think)
}
.map_err(|e| e.to_string())?;
let n_prompt = prompt.len() as i32;
/* C defaults ctx to 262144 on CUDA; the ids do not depend on ctx size,
* so grow to fit unless -c was explicit. */
let ctx = if args.ctx_set {
args.ctx
} else {
args.ctx.max(n_prompt + args.n_predict + 8)
};
let mut session = session_ready(model, args, ctx)?;
session.sync(&prompt).map_err(|e| e.to_string())?;
let path = args.dump_logprobs.as_deref().unwrap();
let mut fp = std::io::BufWriter::new(
std::fs::File::create(path).map_err(|e| format!("dump-logprobs {path}: {e}"))?,
);
let k = (args.logprobs_top_k.max(1) as usize).min(TOP_K_CAP);
let mut w = |s: String| fp.write_all(s.as_bytes()).map_err(|e| e.to_string());
w(format!(
"{{\n \"source\":\"ds4\",\n \"prompt_tokens\":{n_prompt},\n \"ctx\":{ctx},\n \"top_k\":{k},\n \"steps\":[\n"
))?;
let mut max_tokens = args.n_predict;
let room = session.ctx() - session.pos();
if room <= 1 {
max_tokens = 0;
} else if max_tokens > room - 1 {
max_tokens = room - 1;
}
for generated in 0..max_tokens {
let scores = session.top_logprobs(k);
let token = session.argmax();
if generated > 0 {
w(",\n".into())?;
}
w(format!(" {{\"step\":{generated},\"selected\":"))?;
w(json_token(model, token))?;
w(",\"top_logprobs\":[".into())?;
for (i, s) in scores.iter().take_while(|s| s.id >= 0).enumerate() {
if i > 0 {
w(",".into())?;
}
/* Rust shortest-roundtrip floats, not C's %.9g: the md5
* contract reads only the selected ids. */
w(format!(
"{{\"token\":{},\"logit\":{},\"logprob\":{}}}",
json_token(model, s.id),
s.logit,
s.logprob
))?;
}
w("]}".into())?;
if model.token_is_stop(token) {
break;
}
session.eval(token).map_err(|e| e.to_string())?;
}
w("\n ]\n}\n".into())?;
fp.flush().map_err(|e| e.to_string())?;
Ok(0)
}
fn json_token(model: &ds4_core::Model, token: i32) -> String {
let bytes = model.token_text(token).unwrap_or_default();
let mut s = format!("{{\"id\":{token},\"text\":\"");
for &b in &bytes {
match b {
b'"' => s.push_str("\\\""),
b'\\' => s.push_str("\\\\"),
0x20..=0x7e => s.push(b as char),
_ => s.push_str(&format!("\\u{:04x}", b)),
}
}
s.push_str("\",\"bytes\":[");
for (i, &b) in bytes.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(&b.to_string());
}
s.push_str("]}");
s
}
struct LogitsDumpJson<'a> {
model: &'a str,
backend: &'a str,
quant_bits: i32,
prompt_tokens: i32,
ctx: i32,
argmax_token_json: &'a str,
argmax_logit: f32,
logits: &'a [f32],
}
fn backend_name(backend: Backend) -> &'static str {
match backend {
Backend::Cuda => "cuda",
Backend::Metal => "metal",
Backend::Cpu => "cpu",
}
}
fn json_escape_str(s: &str) -> String {
let mut out = String::from("\"");
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c => out.push(c),
}
}
out.push('"');
out
}
fn json_logit(v: f32) -> String {
if v.is_finite() {
format!("{v}")
} else {
"null".into()
}
}
fn format_logits_json(dump: &LogitsDumpJson<'_>) -> String {
let mut body = String::from("{\n \"source\":\"ds4\",\n \"model\":");
body.push_str(&json_escape_str(dump.model));
body.push_str(&format!(
",\n \"backend\":\"{}\",\n \"quant_bits\":{},\n \"prompt_tokens\":{},\n \"ctx\":{},\n \"vocab\":{},\n \"argmax_token\":{},\n \"argmax_logit\":{},\n \"logits\":[",
dump.backend,
dump.quant_bits,
dump.prompt_tokens,
dump.ctx,
dump.logits.len(),
dump.argmax_token_json,
json_logit(dump.argmax_logit),
));
for (i, logit) in dump.logits.iter().enumerate() {
if i > 0 {
body.push(',');
}
if i % 8 == 0 {
body.push_str("\n ");
}
body.push_str(&json_logit(*logit));
}
body.push_str("\n ]\n}\n");
body
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum EvalPlan {
Skip,
Run,
}
fn eval_plan(args: &ShadowArgs) -> EvalPlan {
if args.dump_tokens {
EvalPlan::Skip
} else {
EvalPlan::Run
}
}
fn format_dump_tokens(ids: &[i32], lines: &[(i32, &[u8])]) -> Vec<u8> {
let mut out = Vec::from(b"[".as_slice());
for (i, id) in ids.iter().enumerate() {
if i > 0 {
out.extend_from_slice(b", ");
}
out.extend_from_slice(id.to_string().as_bytes());
}
out.extend_from_slice(b"]\n");
for (id, text) in lines {
out.extend_from_slice(format!("{id:6} ").as_bytes());
out.extend_from_slice(text);
out.push(b'\n');
}
out
}
fn write_dump_tokens(
out: &mut impl std::io::Write,
ids: &[i32],
vocab: &ds4_core::Vocab,
) -> Result<(), String> {
let table = vocab.tokens();
let lines: Vec<(i32, &[u8])> = ids
.iter()
.copied()
.filter_map(|id| {
if id < 0 {
return None;
}
table.get(id as usize).map(|text| (id, text.as_slice()))
})
.collect();
out.write_all(&format_dump_tokens(ids, &lines))
.map_err(|e| e.to_string())
}
fn run_dump_tokens(model_path: &str, text: &str) -> Result<i32, String> {
let id =
ds4_core::identify_gguf(std::path::Path::new(model_path)).map_err(|e| e.to_string())?;
let vocab = ds4_core::Vocab::load_path(std::path::Path::new(model_path), id.shape.family)
.map_err(|e| e.to_string())?;
let ids = vocab.encode_rendered_chat(text);
let mut stdout = std::io::stdout().lock();
write_dump_tokens(&mut stdout, &ids, &vocab)?;
Ok(0)
}
fn run_dump_tokens_cli(args: &ShadowArgs) -> Result<i32, String> {
if args.prompt.is_none() && args.prompt_file.is_none() {
return Err("ds4: --dump-tokens requires -p or --prompt-file".into());
}
let text = prompt_text(args)?;
let model_path = args
.model
.as_deref()
.ok_or_else(|| "missing -m/--model (or pass --help)".to_string())?;
run_dump_tokens(model_path, &text)
}
fn write_logits_file(path: &str, body: &str) -> Result<(), String> {
use std::io::Write;
let mut fp = std::fs::File::create(path)
.map_err(|_| format!("ds4: failed to open --dump-logits file: {path}"))?;
fp.write_all(body.as_bytes()).map_err(|e| e.to_string())?;
fp.flush()
.map_err(|_| format!("ds4: failed to close --dump-logits file: {path}"))?;
Ok(())
}
fn run_logits_dump(
model: &ds4_core::Model,
args: &ShadowArgs,
prompt_text: &str,
) -> Result<i32, String> {
const THINK_NONE: i32 = 0;
const THINK_LOW: i32 = 1;
let think = if args.nothink { THINK_NONE } else { THINK_LOW };
let prompt = if is_rendered_chat_prompt(prompt_text) {
model.tokenize_rendered_chat(prompt_text)
} else {
model.encode_chat_prompt(Some(args.system.as_str()), prompt_text, think)
}
.map_err(|e| e.to_string())?;
let n_prompt = prompt.len() as i32;
let ctx = args.ctx;
let mut session = session_ready(model, args, ctx)?;
session.sync(&prompt).map_err(|e| e.to_string())?;
let vocab = model.vocab().n_vocab() as usize;
let logits = session.copy_logits(vocab).map_err(|e| e.to_string())?;
let argmax = session.argmax();
let argmax_logit = if argmax >= 0 {
logits.get(argmax as usize).copied().unwrap_or(0.0)
} else {
0.0
};
let argmax_token_json = json_token(model, argmax);
let path = args
.dump_logits
.as_deref()
.ok_or_else(|| "ds4: missing value for --dump-logits".to_string())?;
let body = format_logits_json(&LogitsDumpJson {
model: args.model.as_deref().unwrap_or("ds4flash.gguf"),
backend: backend_name(args.backend),
quant_bits: model.routed_quant_bits(),
prompt_tokens: n_prompt,
ctx,
argmax_token_json: &argmax_token_json,
argmax_logit,
logits: &logits,
});
write_logits_file(path, &body)?;
Ok(0)
}
pub fn help_text(name: &str) -> String {
format!(
"\
{name} — Rust shadow of the C host (same C inference core)
Usage:
{name} --help
{name} -m MODEL --identify
{name} -m MODEL --inventory
{name} --bind-names VARIANT
{name} --layout VARIANT
{name} -m MODEL --bind-plan
{name} -m MODEL --validate
{name} -m MODEL --tokenize FAMILY CMD [ARG]
{name} --session-plan CMD [ARGS...]
{name} --session-payload CMD [ARGS...]
{name} -m MODEL [--backend cuda|cpu|metal] [-c CTX] [--lifecycle]
{name} -m MODEL (-p PROMPT | --prompt-file FILE) [-n N]
[--think|--nothink] [--temp F --top-p F --min-p F --seed N]
{name} -m MODEL --token-ids 1,2,3 [--predict N]
{name} -m MODEL [--mtp GGUF] [--dspark GGUF] ...
{name} --cuda -m MODEL --temp 0 -n N [--nothink] --dump-logprobs F \\
--logprobs-top-k K (-p PROMPT | --prompt-file FILE)
{name} --cuda -m MODEL [--nothink] --dump-logits F \\
(-p PROMPT | --prompt-file FILE)
{name} -m MODEL --dump-tokens (-p PROMPT | --prompt-file FILE)
C-compatible flags (same names as `ds4 --help`):
-m, --model FILE GGUF model path. Default: ds4flash.gguf
--ssd-streaming Stream GLM CUDA routed experts from SSD
--ssd-streaming-cache-experts auto|N|GB Auto budget, slots or GiB
--ssd-streaming-cold Advise eviction of read expert pages
--mtp FILE Optional MTP support GGUF
--mtp-draft N Maximum MTP draft tokens. Default: 1
--mtp-margin F MTP verifier margin. Default: 3
-c, --ctx N Context size. Default: 32768
--metal, --cuda, --cpu Select backend explicitly
--backend NAME metal, cuda, or cpu
-t, --threads N CPU helper threads
-p, --prompt TEXT Prompt to generate from
--prompt-file FILE Read the prompt text from FILE
-sys, --system TEXT System prompt
-n, --tokens N Maximum tokens to generate. Default: 50000
--temp F --top-p F --min-p F --seed N
--think --nothink
--dump-tokens --dump-logits FILE --dump-logprobs FILE --logprobs-top-k N
-h, --help Show this help
--mtp attaches DeepSeek, Inkling or Step predictors; --dspark takes DeepSeek or Naive.
IQuest-Q1 uses embedded MTP: --cuda --mtp-draft 2..7 --temp 0, with no --mtp file.
GLM-5.3 uses embedded MTP: --cuda --mtp-draft 2..3 --temp 0, with no --mtp file.
--dump-logprobs mirrors the C CLI proof loop (chat-template encode via
the engine, argmax decode, host stop set); ctx grows to fit prompt+n
unless -c is explicit. On the V4.1 route (--gen-ids) the dump carries the
selected ids with top_k 0: that bridge hands the run no logits.
--dump-logits writes full next-token logits as JSON after prompt prefill.
--dump-tokens tokenizes -p/--prompt-file exactly as written, then exits
without inference.
One-shot sampling uses the native C sampler; an explicit --seed is
reproducible across the C and Rust hosts.
Thinking tags follow the C contract: TTY greys the body; pipes stay uncolored.
--tokens is the C-compatible output budget; --token-ids is the shadow-only
raw token-ID diagnostic.
Distributed worker/coordinator options use the frozen C DS4D runtime behind
the Rust-owned CLI/model lifecycle in this first production integration slice:
{dist_usage}
--identify mmaps GGUF metadata only (no CUDA, no ds4_bridge_model_open).
--inventory mmaps the tensor directory + split remap (no CUDA, no engine open).
--bind-names dumps the host weights_bind catalog (no GGUF, no engine open).
--layout dumps the host weights_validate_layout table (no GGUF, no engine open).
--bind-plan resolves a catalog against the host inventory (no CUDA).
With --bind-names VARIANT, --bind-plan uses that catalog (including
mtp-flash / dspark-pro) instead of identifying the GGUF family.
--validate runs host-owned config_validate (no CUDA, no engine open).
VARIANT is flash|pro|solar-open2|motif3|exaone-moe|dots3-note|qwen4exp|glm5-next|k2-horizon
or DeepSeek sibling mtp-flash|mtp-pro|dspark-flash|dspark-pro.
--tokenize loads the host-owned GPT-2/BPE vocab (no engine open).
--session-plan dumps the host session ledger (no engine open).
--session-payload dumps the host DSV4 prefix codec (no engine open).
FAMILY is deepseek4|motif3|solar-open2|exaone-moe|dots3-note|qwen4exp|glm5-next|k2-horizon|iquest_q1.
CMD is specials | encode HEX | render HEX | decode ID | stop ID.
",
dist_usage = ds4_dist::USAGE,
)
}
pub fn run(name: &str, mut args: ShadowArgs) -> Result<i32, String> {
if args.help {
print!("{}", help_text(name));
return Ok(0);
}
if eval_plan(&args) == EvalPlan::Skip {
return run_dump_tokens_cli(&args);
}
if args.session_plan {
let cmd = args
.session_cmd
.as_deref()
.ok_or_else(|| "--session-plan requires CMD".to_string())?;
let argv: Vec<&str> = args.session_args.iter().map(String::as_str).collect();
print!("{}", ds4_core::session_dump_cmd(cmd, &argv));
return Ok(0);
}
if args.session_payload {
let cmd = args
.payload_cmd
.as_deref()
.ok_or_else(|| "--session-payload requires CMD".to_string())?;
let argv: Vec<&str> = args.payload_args.iter().map(String::as_str).collect();
print!("{}", ds4_core::payload_dump_cmd(cmd, &argv));
return Ok(0);
}
if args.layout {
let name = args
.layout_variant
.as_deref()
.ok_or_else(|| "--layout requires VARIANT".to_string())?;
let dump = ds4_core::dump_expected_layouts_variant(name)
.ok_or_else(|| format!("unknown layout variant: {name}"))?;
print!("{dump}");
return Ok(0);
}
if args.bind_names && !args.bind_plan {
let name = args
.bind_names_variant
.as_deref()
.ok_or_else(|| "--bind-names requires VARIANT".to_string())?;
let dump = ds4_core::dump_bind_names_variant(name)
.ok_or_else(|| format!("unknown bind-names variant: {name}"))?;
print!("{dump}");
return Ok(0);
}
let model_path = args
.model
.as_deref()
.ok_or_else(|| "missing -m/--model (or pass --help)".to_string())?;
if args.identify {
let id =
ds4_core::identify_gguf(std::path::Path::new(model_path)).map_err(|e| e.to_string())?;
println!("{}", id.report_line(model_path));
return Ok(0);
}
if args.inventory {
let inv = ds4_core::TensorInventory::open(std::path::Path::new(model_path))
.map_err(|e| e.to_string())?;
print!("{}", inv.dump());
return Ok(0);
}
if args.bind_plan {
let inv = ds4_core::TensorInventory::open(std::path::Path::new(model_path))
.map_err(|e| e.to_string())?;
let (support, shape) = if let Some(name) = args.bind_names_variant.as_deref() {
let (support, v) = ds4_core::catalog_from_bind_name(name)
.ok_or_else(|| format!("unknown bind-names variant: {name}"))?;
(support, ds4_core::shape_for_variant(v))
} else {
let id = ds4_core::identify_gguf(std::path::Path::new(model_path))
.map_err(|e| e.to_string())?;
(None, id.shape)
};
print!(
"{}",
ds4_core::BindPlan::resolve_catalog(support, shape, &inv).dump()
);
return Ok(0);
}
if args.validate {
print!(
"{}",
ds4_core::dump_validate(std::path::Path::new(model_path))
);
return Ok(0);
}
if args.tokenize {
let fam_name = args
.tok_family
.as_deref()
.ok_or_else(|| " --tokenize requires FAMILY".to_string())?;
let family = ds4_core::ModelFamily::from_oracle_name(fam_name)
.ok_or_else(|| format!("unknown tokenizer family: {fam_name}"))?;
let cmd = args
.tok_cmd
.as_deref()
.ok_or_else(|| "--tokenize requires CMD".to_string())?;
let vocab = ds4_core::Vocab::load_path(std::path::Path::new(model_path), family)
.map_err(|e| e.to_string())?;
print!(
"{}",
ds4_core::dump_cmd(&vocab, cmd, args.tok_arg.as_deref().unwrap_or(""))
);
return Ok(0);
}
let worker = args.dist.role == ds4_dist::Role::Worker;
let raw_token_diagnostic = !args.tokens.is_empty() || args.predict > 0;
let generation_text = if !worker && (args.prompt.is_some() || args.prompt_file.is_some()) {
Some(prompt_text(&args)?)
} else {
None
};
let want_repl = !worker
&& args.prompt.is_none()
&& args.prompt_file.is_none()
&& args.dump_logprobs.is_none()
&& args.dump_logits.is_none()
&& !args.dump_tokens
&& !args.lifecycle_only
&& !raw_token_diagnostic;
if !want_repl
&& !worker
&& args.dump_logprobs.is_none()
&& args.dump_logits.is_none()
&& !args.dump_tokens
&& !args.lifecycle_only
&& !raw_token_diagnostic
{
validate_one_shot_args(&args)?;
}
if !worker && args.dump_logits.is_some() && generation_text.is_none() {
return Err("--dump-logits requires -p or --prompt-file".into());
}
/* The V4.1 route has no prompt: its run input is --gen-ids, and the dump
* rides that (V41Dump), so the prompt requirement is the generic one's. */
if !worker
&& args.dump_logprobs.is_some()
&& generation_text.is_none()
&& args.gen_ids.is_none()
{
return Err("--dump-logprobs requires -p or --prompt-file".into());
}
let native_dist = distributed_config(&args.dist);
// Embedded IQuest uses the existing --mtp-draft control without a sidecar.
// Other families retain their current support-artifact activation rules.
let embedded_family = if args.mtp.is_none() && args.dspark.is_none() && args.mtp_draft > 1 {
Some(
ds4_core::identify_gguf(std::path::Path::new(model_path))
.map_err(|e| e.to_string())?
.shape
.family,
)
} else {
None
};
let mut mtp_opts = mtp_open_options(&args, embedded_family)?;
// --vision-gguf: the ds41 tower's sidecar rides the same open slot every
// family uses (the server's ModelOpenOption::Vision, ds4-server-rs.rs:93).
if let Some(path) = args.vision.as_deref() {
mtp_opts.push(ds4_core::ModelOpenOption::Vision(path.to_string()));
}
// --mem-budget-mb (unit Z): the engine's L1 load gate reads the budget
// at open (core_util.c:238-256); arm it before the model open.
if let Some(mb) = args.mem_budget_mb {
ds4_core::set_mem_budget_mb(mb);
}
let model = match native_dist.as_ref() {
Some(config) => ds4_core::Model::open_distributed_options(
model_path,
args.backend,
args.threads,
true,
args.mtp.as_deref(),
args.dspark.as_deref(),
config,
&mtp_opts,
),
None => ds4_core::Model::open_with_support_options(
model_path,
args.backend,
args.threads,
true,
args.mtp.as_deref(),
args.dspark.as_deref(),
&mtp_opts,
),
}
.map_err(|e| e.to_string())?;
args.ctx = session_ctx(&args, ds4_core::caps_from_shape(model.shape()));
/* The engine's --vision-golden gate (cli_main.c:193-195): a run mode of
* its own, before any generate; exactly one --image names the picture. */
if let Some(golden) = args.vision_golden.as_deref() {
return run_v41_vision_golden(&model, &args, golden);
}
if !args.images.is_empty() {
return Err(
"ds4-rs: --image needs --vision-golden (the one-shot image request is not ported yet)"
.into(),
);
}
/* V4.1 (ds41): no ds4_session exists for this family -- the run IS the
* engine's one-shot generate with the host engram feed (v41_run.rs). The
* family's chat rendering is not ported yet, so the run input is the raw
* token ids (the engine's --gen-ids). */
if model.shape().variant == ds4_core::Variant::DeepSeek41Flash {
return run_v41_one_shot(&model, &args, model_path);
}
if worker {
model.boot_prewarm();
return worker_run::run_assembled_worker(&model, args.ctx, &args.dist);
}
if args.dump_logits.is_some() {
return run_logits_dump(
&model,
&args,
generation_text
.as_deref()
.ok_or_else(|| "--dump-logits requires -p or --prompt-file".to_string())?,
);
}
if args.dump_logprobs.is_some() {
return run_logprob_dump(&model, &args, generation_text.as_deref().unwrap());
}
if args.lifecycle_only {
let session = model.session(args.ctx).map_err(|e| e.to_string())?;
println!(
"lifecycle ok backend={:?} ctx={} pos={}",
args.backend,
args.ctx,
session.pos()
);
return Ok(0);
}
if !args.tokens.is_empty() {
let mut session = session_ready(&model, &args, args.ctx)?;
let buf = ds4_core::TokenBuffer::from_tokens(args.tokens);
session.sync(&buf).map_err(|e| e.to_string())?;
print!("{}", session.argmax());
for _ in 0..args.predict {
let token = session.argmax();
session.eval(token).map_err(|e| e.to_string())?;
print!(" {}", session.argmax());
}
println!();
return Ok(0);
}
if args.predict > 0 {
/* Preserve the old diagnostic's no-op behavior when --predict was
* supplied without raw token IDs. */
let _session = model.session(args.ctx).map_err(|e| e.to_string())?;
return Ok(0);
}
if want_repl {
return run_repl(&model, args);
}
run_one_shot(&model, &args, generation_text.as_deref().unwrap())
}
fn run_repl(model: &ds4_core::Model, mut args: ShadowArgs) -> Result<i32, String> {
let mut chat = repl::ReplChat::new(args.nothink, args.ctx);
let vocab = model.vocab();
if model.chat_template().is_some() {
chat.conversation = Some(conversation::Conversation::new(&args.system));
} else if model.family() == ds4_core::ModelFamily::DeepSeek4 {
vocab
.chat_begin(&mut chat.transcript)
.map_err(|e| e.to_string())?;
apply_effort_prefix(vocab, &mut chat, None);
if !args.system.is_empty() {
vocab
.chat_append_message(&mut chat.transcript, "system", args.system.as_bytes())
.map_err(|e| e.to_string())?;
}
} else {
return Err("missing chat_template.jinja or tokenizer.chat_template".into());
}
let mut session = session_ready(model, &args, chat.ctx)?;
let _sigint = repl::InterruptGuard::install();
let history_path = repl::history_file_path();
let tty = repl::stdin_is_tty();
if tty {
repl::init_linenoise_history(&history_path);
}
let mut history = repl::History::load(history_path.clone());
print!("{}", repl::REPL_HELP);
loop {
let line = match repl::read_repl_line("ds4> ")? {
repl::ReplRead::Eof => break,
repl::ReplRead::Interrupted => {
repl::interrupt_clear();
continue;
}
repl::ReplRead::Line(line) => line,
};
let cmd = repl::trim_inplace(&line);
if !cmd.is_empty() {
if tty {
repl::remember_linenoise_line(&history_path, cmd);
} else if history.add(cmd.to_string()) {
let _ = history.save();
}
}
match repl::parse_repl_line(&line) {
Ok(repl::ReplLine::Empty) => {}
Ok(repl::ReplLine::Help) => print!("{}", repl::REPL_HELP),
Ok(repl::ReplLine::Quit) => break,
Ok(repl::ReplLine::Think) => {
chat.think = repl::ReplThinkCmd::Low;
args.nothink = false;
apply_effort_prefix(vocab, &mut chat, Some(&mut session));
println!("{}", chat.think_message());
}
Ok(repl::ReplLine::ThinkMax) => {
chat.think = repl::ReplThinkCmd::Max;
args.nothink = false;
apply_effort_prefix(vocab, &mut chat, Some(&mut session));
println!("{}", chat.think_message());
}
Ok(repl::ReplLine::NoThink) => {
chat.think = repl::ReplThinkCmd::None;
args.nothink = true;
apply_effort_prefix(vocab, &mut chat, Some(&mut session));
println!("{}", chat.think_message());
}
Ok(repl::ReplLine::Ctx(n)) => {
args.ctx = n;
args.ctx_set = true;
chat.ctx = n;
session = session_ready(model, &args, chat.ctx)?;
apply_effort_prefix(vocab, &mut chat, Some(&mut session));
}
Ok(repl::ReplLine::Power(None)) => {
println!("Power: {}%.", session.power());
}
Ok(repl::ReplLine::Power(Some(n))) => {
if session.set_power(n).is_err() {
eprint!("ds4: failed to set /power\n");
} else {
println!("Power: {n}%.");
}
}
Ok(repl::ReplLine::Read(path)) => {
let text = std::fs::read_to_string(&path)
.map_err(|e| format!("ds4: failed to read {path}: {e}"))?;
run_chat_turn(model, &args, &mut chat, &mut session, &text)?;
}
Ok(repl::ReplLine::Prompt(text)) => {
run_chat_turn(model, &args, &mut chat, &mut session, &text)?;
}
Err(err) => eprint!("{}", err.message()),
}
}
Ok(0)
}
fn require_value(flag: &str, value: Option<String>) -> Result<String, String> {
value.ok_or_else(|| format!("{flag} requires a value"))
}
fn parse_i32(flag: &str, value: &str) -> Result<i32, String> {
value
.parse()
.map_err(|_| format!("{flag}: invalid integer {value}"))
}
fn parse_positive_i32(flag: &str, value: &str) -> Result<i32, String> {
value
.parse::<i32>()
.ok()
.filter(|v| *v > 0)
.ok_or_else(|| format!("invalid value for {flag}: {value}"))
}
fn parse_f32_range(flag: &str, value: &str, min: f32, max: f32) -> Result<f32, String> {
let parsed = value
.parse::<f32>()
.ok()
.filter(|v| v.is_finite() && *v >= min && *v <= max)
.ok_or_else(|| format!("invalid value for {flag}: {value}"))?;
Ok(parsed)
}
fn parse_positive_u64(flag: &str, value: &str) -> Result<u64, String> {
value
.parse::<u64>()
.ok()
.filter(|v| *v > 0)
.ok_or_else(|| format!("invalid value for {flag}: {value}"))
}
#[cfg(target_os = "macos")]
fn default_backend() -> Backend {
Backend::Metal
}
#[cfg(not(target_os = "macos"))]
fn default_backend() -> Backend {
Backend::Cuda
}
fn parse_backend(value: &str) -> Result<Backend, String> {
match value {
"cuda" => Ok(Backend::Cuda),
"cpu" => Ok(Backend::Cpu),
"metal" => Ok(Backend::Metal),
other => Err(format!("unknown backend: {other}")),
}
}
fn parse_tokens(value: &str) -> Result<Vec<i32>, String> {
if value.is_empty() {
return Ok(Vec::new());
}
value
.split(',')
.map(|part| {
part.trim()
.parse()
.map_err(|_| format!("invalid token id: {part}"))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn format_fragments(format_thinking: bool, parts: &[&[u8]]) -> Vec<u8> {
format_fragments_colored(format_thinking, false, parts)
}
fn format_fragments_colored(
format_thinking: bool,
use_color: bool,
parts: &[&[u8]],
) -> Vec<u8> {
let mut printer = TokenPrinter::with_color(format_thinking, use_color);
let mut out = Vec::new();
for part in parts {
printer.write_text(&mut out, part).unwrap();
}
printer.finish(&mut out).unwrap();
out
}
fn args(parts: &[&str]) -> Vec<String> {
std::iter::once("ds4-rs".to_string())
.chain(parts.iter().map(|s| (*s).to_string()))
.collect()
}
#[test]
fn parses_lifecycle() {
let parsed = parse_args(args(&["-m", "m.gguf", "--lifecycle", "-c", "4096"])).unwrap();
assert_eq!(parsed.model.as_deref(), Some("m.gguf"));
assert_eq!(parsed.ctx, 4096);
assert!(parsed.lifecycle_only);
assert_eq!(parsed.backend, default_backend());
}
#[test]
fn ssd_budget_uses_context() {
let parsed = parse_args(args(&["--ssd-streaming", "-c", "1048576"])).unwrap();
let options = mtp_open_options(&parsed, None).unwrap();
assert!(options.iter().any(|option| matches!(option,
ModelOpenOption::ServingBudget(req) if req.ctx == 1048576
&& req.max_seqs == ds4_core::MaxSeqs::Off)));
assert!(ds4_core::check_ssd_options(
&options,
Some(ds4_core::ModelFamily::Glm53),
ds4_core::Backend::Cuda,
None,
)
.is_ok());
}
#[test]
fn parses_ssd_streaming() {
let parsed = parse_args(args(&[
"--ssd-streaming",
"--ssd-streaming-cache-experts",
"1.5GB",
"--ssd-streaming-cold",
]));
let parsed = parsed.unwrap();
assert_eq!(
parsed.ssd_options,
vec![
ds4_core::ModelOpenOption::SsdStreaming,
ds4_core::ModelOpenOption::SsdCacheBytes(3 * (1 << 29)),
ds4_core::ModelOpenOption::SsdStreamingCold,
]
);
let options = mtp_open_options(&parsed, None).unwrap();
assert_eq!(options[..parsed.ssd_options.len()], parsed.ssd_options);
assert!(
matches!(options.last(), Some(ModelOpenOption::ServingBudget(req))
if req.ctx == parsed.ctx && req.max_seqs == ds4_core::MaxSeqs::Off)
);
}
#[test]
fn parses_distributed_worker_options_for_native_oracle_runtime() {
let parsed = parse_args(args(&[
"--role",
"worker",
"--layers",
"21:output",
"--listen",
"0.0.0.0",
"7100",
"--coordinator",
"10.0.0.1",
"7000",
]))
.unwrap();
assert_eq!(parsed.dist.role, ds4_dist::Role::Worker);
assert_eq!(parsed.dist.layers.start, 21);
assert!(parsed.dist.layers.has_output);
assert_eq!(parsed.dist.listen_host.as_deref(), Some("0.0.0.0"));
assert_eq!(parsed.dist.listen_port, 7100);
assert_eq!(parsed.dist.coordinator_host.as_deref(), Some("10.0.0.1"));
assert_eq!(parsed.dist.coordinator_port, 7000);
let native = distributed_config(&parsed.dist).unwrap();
assert_eq!(native.role, ds4_core::DistributedRole::Worker);
assert_eq!(native.layer_start, 21);
assert_eq!(native.layer_end, u32::MAX);
assert!(native.has_output);
}
#[test]
fn worker_role_and_layers_hello_uses_bound_listen_port() {
// Given: worker role + layers, listen port unset
let parsed = parse_args(args(&[
"--role",
"worker",
"--layers",
"20:output",
"--coordinator",
"127.0.0.1",
"7000",
]))
.unwrap();
assert_eq!(parsed.dist.role, ds4_dist::Role::Worker);
assert!(parsed.dist.layers.set);
// When: bind from listen_host / listen_port (0 if unset) and plan HELLO
let requested = worker_run::worker_listen_port(parsed.dist.listen_port);
let (_listener, port) =
ds4_dist::open_data_listener(parsed.dist.listen_host.as_deref(), requested).unwrap();
let meta = session_exec::slice_meta(7, &ds4_core::SHAPE_FLASH, 4096, &parsed.dist.layers);
let plan = worker_run::worker_plan(&meta, 2, u32::from(port), ds4_core::SHAPE_FLASH.name);
// Then: HELLO carries the bound nonzero data port
assert_eq!(requested, 0);
assert_ne!(plan.hello.listen_port, 0);
assert_eq!(plan.hello.listen_port, u32::from(port));
}
#[test]
fn worker_missing_coordinator_host_errors_via_validate_options() {
// Given: worker role + layers, no coordinator host
let err = parse_args(args(&["--role", "worker", "--layers", "20:output"])).unwrap_err();
// When/Then: validate_options (via parse_args) still requires coordinator
assert_eq!(err, "--role worker requires --coordinator HOST PORT");
let mut opt = ds4_dist::Options {
role: ds4_dist::Role::Worker,
..ds4_dist::Options::default()
};
opt.layers.set = true;
assert_eq!(
ds4_dist::validate_options(&opt).unwrap_err(),
"--role worker requires --coordinator HOST PORT"
);
}
#[test]
fn maps_distributed_coordinator_options_to_native_oracle_runtime() {
let parsed = parse_args(args(&[
"--role",
"coordinator",
"--layers",
"0:20",
"--listen",
"127.0.0.1",
"7000",
"--dist-prefill-chunk",
"4096",
"--dist-prefill-window",
"4",
"--dist-activation-bits",
"16",
]))
.unwrap();
let native = distributed_config(&parsed.dist).unwrap();
assert_eq!(native.role, ds4_core::DistributedRole::Coordinator);
assert_eq!(native.layer_start, 0);
assert_eq!(native.layer_end, 20);
assert_eq!(native.prefill_chunk, 4096);
assert_eq!(native.prefill_window, 4);
assert_eq!(native.activation_bits, 16);
}
#[test]
fn uses_c_cli_core_defaults() {
let parsed = parse_args(args(&[])).unwrap();
assert_eq!(parsed.model.as_deref(), Some("ds4flash.gguf"));
assert_eq!(parsed.backend, default_backend());
assert_eq!(parsed.ctx, 32768);
assert_eq!(parsed.n_predict, 50000);
assert_eq!(parsed.system, "You are a helpful assistant");
assert_eq!(parsed.temp, 1.0);
assert_eq!(parsed.top_p, 1.0);
assert_eq!(parsed.min_p, 0.05);
assert_eq!(parsed.seed, 0);
assert!(!parsed.nothink);
assert_eq!(parsed.mtp_draft, 1);
assert_eq!(parsed.mtp_margin, 3.0);
}
#[test]
fn parses_mtp_draft_and_margin() {
let parsed = parse_args(args(&[
"--mtp",
"draft.gguf",
"--mtp-draft",
"2",
"--mtp-margin",
"4.5",
]))
.unwrap();
assert_eq!(parsed.mtp.as_deref(), Some("draft.gguf"));
assert_eq!(parsed.mtp_draft, 2);
assert_eq!(parsed.mtp_margin, 4.5);
}
#[test]
fn rejects_mtp_draft_and_margin_outside_c_ranges() {
for bad in [
["--mtp-draft", "0"],
["--mtp-draft", "-1"],
["--mtp-draft", "x"],
["--mtp-margin", "-0.1"],
["--mtp-margin", "1000.1"],
["--mtp-margin", "NaN"],
] {
assert!(parse_args(args(&bad)).is_err(), "accepted {bad:?}");
}
}
#[test]
fn parses_c_cli_sampling_options() {
let parsed = parse_args(args(&[
"--temp", "0.8", "--top-p", "0.9", "--min-p", "0.02", "--seed", "424242",
]))
.unwrap();
assert_eq!(parsed.temp, 0.8);
assert_eq!(parsed.top_p, 0.9);
assert_eq!(parsed.min_p, 0.02);
assert_eq!(parsed.seed, 424242);
}
#[test]
fn rejects_sampling_outside_c_cli_ranges() {
for bad in [
["--temp", "NaN"],
["--temp", "101"],
["--top-p", "-0.1"],
["--top-p", "1.1"],
["--min-p", "-0.1"],
["--min-p", "1.1"],
["--seed", "0"],
] {
assert!(parse_args(args(&bad)).is_err(), "accepted {bad:?}");
}
}
#[test]
fn parses_output_budget_and_explicit_raw_token_ids() {
let parsed = parse_args(args(&[
"--tokens",
"17",
"--token-ids",
"1, 2,3",
"--predict",
"4",
]))
.unwrap();
assert_eq!(parsed.n_predict, 17);
assert_eq!(parsed.tokens, vec![1, 2, 3]);
assert_eq!(parsed.predict, 4);
}
#[test]
fn parses_token_ids() {
let parsed = parse_args(args(&["--token-ids", "1, 2,3", "--predict", "4"])).unwrap();
assert_eq!(parsed.tokens, vec![1, 2, 3]);
assert_eq!(parsed.predict, 4);
}
#[test]
fn raw_token_ids_have_an_actionable_tokens_collision_error() {
let err = parse_args(args(&["--tokens", "1,2,3"])).unwrap_err();
assert!(err.contains("use --token-ids for raw token IDs"), "{err}");
}
#[test]
fn rejects_non_positive_c_cli_integer_ranges() {
assert_eq!(
parse_args(args(&["--tokens", "0"])).unwrap_err(),
"invalid value for --tokens: 0"
);
assert_eq!(
parse_args(args(&["--ctx", "-1"])).unwrap_err(),
"invalid value for --ctx: -1"
);
}
#[test]
fn rejects_more_than_one_prompt_source() {
assert_eq!(
parse_args(args(&["-p", "one", "--prompt-file", "two.txt"])).unwrap_err(),
"specify only one prompt source"
);
assert_eq!(
parse_args(args(&["--prompt-file", "one.txt", "-p", "two"])).unwrap_err(),
"specify only one prompt source"
);
}
#[test]
fn parses_system_and_last_think_mode_wins() {
let thinking = parse_args(args(&[
"-sys",
"system",
"--nothink",
"--think",
"-p",
"hello",
]))
.unwrap();
assert_eq!(thinking.system, "system");
assert!(!thinking.nothink);
let direct = parse_args(args(&["--think", "--nothink", "-p", "hello"])).unwrap();
assert!(direct.nothink);
}
#[test]
fn validates_only_supported_one_shot_routes() {
let greedy = parse_args(args(&["-p", "hello", "--temp", "0", "--nothink"])).unwrap();
assert!(validate_one_shot_args(&greedy).is_ok());
let sampled = parse_args(args(&["-p", "hello", "--temp", "0.5", "--nothink"])).unwrap();
assert!(validate_one_shot_args(&sampled).is_ok());
let thinking = parse_args(args(&["-p", "hello", "--temp", "0", "--think"])).unwrap();
assert!(validate_one_shot_args(&thinking).is_ok());
let mtp = parse_args(args(&[
"-p",
"hello",
"--temp",
"0",
"--nothink",
"--mtp",
"mtp.gguf",
"--mtp-draft",
"2",
]))
.unwrap();
assert!(validate_one_shot_args(&mtp).is_ok());
assert!(use_mtp_spec(mtp.temp, mtp.mtp.as_deref(), mtp.mtp_draft));
let dspark = parse_args(args(&[
"-p",
"hello",
"--temp",
"0",
"--nothink",
"--dspark",
"draft.gguf",
]))
.unwrap();
assert!(validate_one_shot_args(&dspark).is_ok());
assert_eq!(dspark.dspark.as_deref(), Some("draft.gguf"));
}
#[test]
fn mtp_spec_follows_c_gates() {
assert!(!use_mtp_spec(0.0, None, 2));
assert!(!use_mtp_spec(0.0, Some("mtp.gguf"), 1));
assert!(!use_mtp_spec(0.5, Some("mtp.gguf"), 2));
assert!(use_mtp_spec(0.0, Some("mtp.gguf"), 2));
}
#[test]
fn iquest_embedded_mtp_passes_explicit_draft_controls() {
let parsed = parse_args(args(&["--temp", "0", "--mtp-draft", "3", "-p", "hello"])).unwrap();
assert!(parsed.mtp.is_none());
assert!(
mtp_open_options(&parsed, Some(ds4_core::ModelFamily::IQuestQ1))
.unwrap()
.iter()
.any(|option| matches!(option, ModelOpenOption::MtpDraftTokens(3)))
);
assert!(
mtp_open_options(&parsed, Some(ds4_core::ModelFamily::DeepSeek4))
.unwrap()
.is_empty()
);
assert!(mtp_open_options(&parsed, None).unwrap().is_empty());
let too_many = parse_args(args(&["--mtp-draft", "8", "-p", "hello"])).unwrap();
assert!(mtp_open_options(&too_many, Some(ds4_core::ModelFamily::IQuestQ1)).is_err());
assert!(use_iquest_spec(0.0, ds4_core::ModelFamily::IQuestQ1, 3));
assert!(use_iquest_spec(0.0, ds4_core::ModelFamily::IQuestQ1, 7));
assert!(!use_iquest_spec(0.5, ds4_core::ModelFamily::IQuestQ1, 3));
assert!(!use_iquest_spec(0.0, ds4_core::ModelFamily::IQuestQ1, 1));
assert!(!use_iquest_spec(0.0, ds4_core::ModelFamily::IQuestQ1, 8));
assert!(!use_iquest_spec(0.0, ds4_core::ModelFamily::DeepSeek4, 3));
}
#[test]
fn glm_embedded_mtp_controls() {
let parsed = parse_args(args(&["--mtp-draft", "3", "--mtp-margin", "2.5"])).unwrap();
let options = mtp_open_options(&parsed, Some(ds4_core::ModelFamily::Glm53)).unwrap();
assert!(options.contains(&ModelOpenOption::MtpDraftTokens(3)));
assert!(options.contains(&ModelOpenOption::MtpMargin(2.5)));
assert!(use_glm_spec(0.0, ds4_core::ModelFamily::Glm53, 3));
assert!(!use_glm_spec(0.5, ds4_core::ModelFamily::Glm53, 3));
assert!(!use_glm_spec(0.0, ds4_core::ModelFamily::Glm53, 1));
let mut too_many = parsed;
too_many.mtp_draft = 4;
assert!(mtp_open_options(&too_many, Some(ds4_core::ModelFamily::Glm53)).is_err());
}
#[test]
fn glm_vq_context_defaults_to_native() {
let vq = ds4_core::serving_caps(
ds4_core::ModelFamily::Glm53Vq,
ds4_core::Variant::Glm53FlashVq,
);
let omitted = parse_args(args(&[])).unwrap();
assert_eq!(session_ctx(&omitted, vq), 1_048_576);
let explicit = parse_args(args(&["-c", "16384"])).unwrap();
assert_eq!(session_ctx(&explicit, vq), 16_384);
let source =
ds4_core::serving_caps(ds4_core::ModelFamily::Glm53, ds4_core::Variant::Glm53Flash);
assert_eq!(session_ctx(&omitted, source), omitted.ctx);
}
#[test]
fn glm_vq_embedded_mtp_controls() {
let parsed = parse_args(args(&["--mtp-draft", "2"])).unwrap();
let options = mtp_open_options(&parsed, Some(ds4_core::ModelFamily::Glm53Vq)).unwrap();
assert!(options.contains(&ModelOpenOption::MtpDraftTokens(2)));
assert!(use_glm_spec(0.0, ds4_core::ModelFamily::Glm53Vq, 2));
assert!(!use_glm_spec(0.0, ds4_core::ModelFamily::Glm53Vq, 1));
}
#[test]
fn iquest_embedded_mtp_passes_confidence_margin() {
let parsed = parse_args(args(&[
"--mtp-draft",
"3",
"--mtp-margin",
"1.75",
"-p",
"hello",
]))
.unwrap();
let controls = mtp_open_options(&parsed, Some(ds4_core::ModelFamily::IQuestQ1)).unwrap();
assert!(controls
.iter()
.any(|o| matches!(o, ModelOpenOption::MtpMargin(m) if *m == 1.75)));
}
#[test]
fn dspark_uses_spec_controls() {
let parsed = parse_args(args(&[
"--dspark",
"draft.gguf",
"--mtp-draft",
"6",
"--mtp-margin",
"0",
]))
.unwrap();
let controls = mtp_open_options(&parsed, None).unwrap();
assert!(controls
.iter()
.any(|o| matches!(o, ModelOpenOption::MtpDraftTokens(6))));
assert!(controls
.iter()
.any(|o| matches!(o, ModelOpenOption::MtpMargin(m) if *m == 0.0)));
assert!(use_naive_spec(0.0, parsed.dspark.as_deref(), 1));
assert!(use_naive_spec(0.0, parsed.dspark.as_deref(), 6));
assert!(!use_naive_spec(0.5, parsed.dspark.as_deref(), 6));
assert!(!use_naive_spec(0.0, None, 6));
}
#[test]
fn v41_run_flags_parse() {
let parsed = parse_args(args(&[
"--gen-ids",
"p1.ids",
"--engram-dir",
"/mnt/engram",
"--zchain",
"/mnt/sidecar",
"--posttrain",
"/mnt/posttrain",
"--mem-budget-mb",
"110000",
"--no-dspark",
"--dspark-verify",
"5",
"--no-graph",
"--emit-trace",
"--v41-prof",
"--v41-no-engram",
]))
.unwrap();
assert_eq!(parsed.gen_ids.as_deref(), Some("p1.ids"));
assert_eq!(parsed.engram_dir.as_deref(), Some("/mnt/engram"));
assert_eq!(parsed.zchain.as_deref(), Some("/mnt/sidecar"));
assert_eq!(parsed.posttrain.as_deref(), Some("/mnt/posttrain"));
assert_eq!(parsed.mem_budget_mb, Some(110000));
assert!(parsed.no_dspark);
assert_eq!(parsed.dspark_verify, 5);
assert!(parsed.no_graph);
assert!(parsed.emit_trace);
assert!(parsed.v41_prof);
assert!(parsed.no_engram);
}
#[test]
fn v41_vision_gate_flags_parse() {
let parsed = parse_args(args(&[
"--vision-gguf",
"s.gguf",
"--image",
"a.png",
"--image",
"b.jpeg",
"--vision-golden",
"g.bin",
]))
.unwrap();
assert_eq!(parsed.vision.as_deref(), Some("s.gguf"));
assert_eq!(parsed.images, ["a.png", "b.jpeg"]);
assert_eq!(parsed.vision_golden.as_deref(), Some("g.bin"));
// The server's alternate spelling rides the same slot.
let parsed = parse_args(args(&["--vision", "s.gguf"])).unwrap();
assert_eq!(parsed.vision.as_deref(), Some("s.gguf"));
assert!(parsed.images.is_empty());
assert!(parsed.vision_golden.is_none());
}
#[test]
fn missing_prompt_is_repl_not_one_shot() {
let parsed = parse_args(args(&[])).unwrap();
assert!(parsed.prompt.is_none());
assert!(parsed.prompt_file.is_none());
assert!(validate_one_shot_args(&parsed)
.unwrap_err()
.contains("one-shot generation requires -p or --prompt-file"));
}
#[test]
fn clamps_generation_to_the_remaining_context() {
assert_eq!(generation_limit(32768, 100, 50000), 32668);
assert_eq!(generation_limit(128, 127, 10), 1);
assert_eq!(generation_limit(128, 128, 10), 0);
assert_eq!(generation_limit(128, 120, 4), 4);
assert_eq!(sampled_generation_limit(128, 127, 10), 0);
assert_eq!(sampled_generation_limit(128, 126, 10), 1);
assert_eq!(sampled_generation_limit(128, 120, 4), 4);
}
#[test]
fn detects_rendered_chat_prompt() {
assert!(is_rendered_chat_prompt(
"<|begin▁of▁sentence|>already rendered"
));
assert!(!is_rendered_chat_prompt("plain user prompt"));
}
#[test]
fn formats_thinking_tags_split_across_pieces() {
assert_eq!(
format_fragments(true, &[b"<thi", b"nk>plan", b"</thi", b"nk>answer"],),
b"plan\nanswer\n"
);
assert_eq!(format_fragments(true, &[b"<thi", b"x"]), b"<thix\n");
}
#[test]
fn preserves_c_thinking_newline_and_finish_rules() {
assert_eq!(
format_fragments(true, &[b"</think>", b"answer\n"]),
b"answer\n"
);
assert_eq!(
format_fragments(true, &[b"plan\n</think>", b"\nanswer"],),
b"plan\n\nanswer\n"
);
assert_eq!(format_fragments(true, &[b"plan</thi"]), b"plan</thi\n");
assert_eq!(format_fragments(true, &[]), b"");
}
#[test]
fn leaves_nothink_output_unformatted() {
assert_eq!(
format_fragments(false, &[b"<thi", b"nk>x</think>"]),
b"<think>x</think>\n"
);
}
#[test]
fn tty_thinking_body_uses_c_grey_ansi() {
assert_eq!(
format_fragments_colored(true, true, &[b"<thi", b"nk>plan", b"</thi", b"nk>answer"],),
b"\x1b[90mplan\x1b[0m\nanswer\n"
);
assert_eq!(
format_fragments_colored(true, true, &[b"plan</think>answer"]),
b"\x1b[90mplan\x1b[0m\nanswer\n"
);
assert_eq!(
format_fragments_colored(true, true, &[b"plan"]),
b"\x1b[90mplan\x1b[0m\n"
);
}
#[test]
fn pipe_thinking_output_has_no_esc() {
let out = format_fragments_colored(true, false, &[b"<think>plan</think>answer"]);
assert!(!out.contains(&0x1b), "pipe must not emit ESC, got {out:?}");
assert_eq!(out, b"plan\nanswer\n");
assert!(!format_fragments(true, &[b"<think>plan</think>answer"]).contains(&0x1b));
}
#[test]
fn rejects_unknown() {
assert!(parse_args(args(&["--nope"])).is_err());
}
#[test]
fn parses_identify() {
let parsed = parse_args(args(&["-m", "m.gguf", "--identify"])).unwrap();
assert!(parsed.identify);
assert_eq!(parsed.model.as_deref(), Some("m.gguf"));
}
#[test]
fn parses_inventory() {
let parsed = parse_args(args(&["-m", "m.gguf", "--inventory"])).unwrap();
assert!(parsed.inventory);
}
#[test]
fn parses_tokenize() {
let parsed = parse_args(args(&[
"-m",
"m.gguf",
"--tokenize",
"motif3",
"encode",
"6869",
]))
.unwrap();
assert!(parsed.tokenize);
assert_eq!(parsed.tok_family.as_deref(), Some("motif3"));
assert_eq!(parsed.tok_cmd.as_deref(), Some("encode"));
assert_eq!(parsed.tok_arg.as_deref(), Some("6869"));
}
#[test]
fn parses_session_plan() {
let parsed =
parse_args(args(&["--session-plan", "rewrite", "1024", "1100", "1024"])).unwrap();
assert!(parsed.session_plan);
assert_eq!(parsed.session_cmd.as_deref(), Some("rewrite"));
assert_eq!(parsed.session_args, vec!["1024", "1100", "1024"]);
}
#[test]
fn parses_session_payload() {
let parsed = parse_args(args(&["--session-payload", "encode-deepseek"])).unwrap();
assert!(parsed.session_payload);
assert_eq!(parsed.payload_cmd.as_deref(), Some("encode-deepseek"));
assert!(parsed.payload_args.is_empty());
}
#[test]
fn parses_bind_names() {
let parsed = parse_args(args(&["--bind-names", "motif3"])).unwrap();
assert!(parsed.bind_names);
assert_eq!(parsed.bind_names_variant.as_deref(), Some("motif3"));
}
#[test]
fn parses_bind_names_support() {
let parsed = parse_args(args(&["--bind-names", "mtp-flash"])).unwrap();
assert!(parsed.bind_names);
assert_eq!(parsed.bind_names_variant.as_deref(), Some("mtp-flash"));
}
#[test]
fn parses_layout_support() {
let parsed = parse_args(args(&["--layout", "dspark-pro"])).unwrap();
assert!(parsed.layout);
assert_eq!(parsed.layout_variant.as_deref(), Some("dspark-pro"));
}
#[test]
fn parses_bind_plan() {
let parsed = parse_args(args(&["-m", "m.gguf", "--bind-plan"])).unwrap();
assert!(parsed.bind_plan);
assert_eq!(parsed.model.as_deref(), Some("m.gguf"));
}
#[test]
fn parses_bind_plan_with_support_catalog() {
let parsed = parse_args(args(&[
"-m",
"mtp.gguf",
"--bind-names",
"mtp-flash",
"--bind-plan",
]))
.unwrap();
assert!(parsed.bind_plan);
assert!(parsed.bind_names);
assert_eq!(parsed.bind_names_variant.as_deref(), Some("mtp-flash"));
assert_eq!(parsed.model.as_deref(), Some("mtp.gguf"));
}
#[test]
fn parses_validate() {
let parsed = parse_args(args(&["-m", "m.gguf", "--validate"])).unwrap();
assert!(parsed.validate);
assert_eq!(parsed.model.as_deref(), Some("m.gguf"));
}
#[test]
fn parses_layout() {
let parsed = parse_args(args(&["--layout", "flash"])).unwrap();
assert!(parsed.layout);
assert_eq!(parsed.layout_variant.as_deref(), Some("flash"));
}
#[test]
fn parses_dump_logits() {
let parsed = parse_args(args(&["--dump-logits", "/tmp/logits.json"])).unwrap();
assert_eq!(parsed.dump_logits.as_deref(), Some("/tmp/logits.json"));
assert!(parsed.dump_logprobs.is_none());
}
#[test]
fn help_contains_dump_logits() {
assert!(help_text("ds4-rs").contains("--dump-logits"));
}
#[test]
fn help_names_k2_horizon_variant_and_tokenizer_alias() {
let help = help_text("ds4-rs");
assert!(help.contains("k2-horizon"));
}
#[test]
fn dump_logits_missing_value_errors_like_c() {
assert_eq!(
parse_args(args(&["--dump-logits"])).unwrap_err(),
"ds4: missing value for --dump-logits"
);
}
#[test]
fn dump_logits_missing_file_errors_like_c() {
let dir = std::env::temp_dir().join("ds4-dump-logits-not-a-file");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.to_str().unwrap();
let err = write_logits_file(path, "{}").unwrap_err();
assert_eq!(
err,
format!("ds4: failed to open --dump-logits file: {path}")
);
}
#[test]
fn v41_dump_records_selected_ids() {
let path = std::env::temp_dir().join("ds4-v41-dump-test.json");
let path = path.to_str().unwrap();
let mut d = V41Dump::open(path, 3).unwrap();
d.push("{\"id\":7,\"text\":\"a\"}").unwrap();
d.push("{\"id\":9,\"text\":\"b\"}").unwrap();
d.close().unwrap();
/* Byte-exact: the "selected" field is what the proof harness's
* selected_token_ids_md5 regex hashes, and the envelope matches the
* generic dump so the same reader takes both. */
assert_eq!(
std::fs::read_to_string(path).unwrap(),
"{\n \"source\":\"ds4-v41\",\n \"prompt_tokens\":3,\n \"top_k\":0,\n \"steps\":[\n {\"step\":0,\"selected\":{\"id\":7,\"text\":\"a\"},\"top_logprobs\":[]},\n {\"step\":1,\"selected\":{\"id\":9,\"text\":\"b\"},\"top_logprobs\":[]}\n ]\n}\n"
);
}
#[test]
fn dump_logits_json_matches_c_shape() {
let body = format_logits_json(&LogitsDumpJson {
model: "m.gguf",
backend: "cuda",
quant_bits: 2,
prompt_tokens: 3,
ctx: 128,
argmax_token_json: "{\"id\":1,\"text\":\"a\",\"bytes\":[97]}",
argmax_logit: 1.5,
logits: &[1.0, 1.5, f32::INFINITY],
});
assert_eq!(
body,
"{\n \"source\":\"ds4\",\n \"model\":\"m.gguf\",\n \"backend\":\"cuda\",\n \"quant_bits\":2,\n \"prompt_tokens\":3,\n \"ctx\":128,\n \"vocab\":3,\n \"argmax_token\":{\"id\":1,\"text\":\"a\",\"bytes\":[97]},\n \"argmax_logit\":1.5,\n \"logits\":[\n 1,1.5,null\n ]\n}\n"
);
}
#[test]
fn parses_dump_tokens() {
let parsed = parse_args(args(&["--dump-tokens", "-p", "hi"])).unwrap();
assert!(parsed.dump_tokens);
assert_eq!(parsed.prompt.as_deref(), Some("hi"));
assert!(parsed.dump_logits.is_none());
assert!(parsed.dump_logprobs.is_none());
}
#[test]
fn help_contains_dump_tokens() {
assert!(help_text("ds4-rs").contains("--dump-tokens"));
}
/// C `ds4 --help` flags the shadow already parses (8.3 claimed modes).
/// `--mtp ` keeps `--mtp-draft` from counting as `--mtp`.
const CLAIMED_C_CLI_FLAGS: &[&str] = &[
"-m, --model",
"--mtp ",
"--mtp-draft",
"--mtp-margin",
"-c, --ctx",
"--metal",
"--cuda",
"--cpu",
"--backend",
"-t, --threads",
"-p, --prompt",
"--prompt-file",
"-sys, --system",
"-n, --tokens",
"--temp",
"--top-p",
"--min-p",
"--seed",
"--think",
"--nothink",
"--dump-tokens",
"--dump-logits",
"--dump-logprobs",
"--logprobs-top-k",
"--role",
"--layers",
"--listen",
"--coordinator",
"--dist-prefill-chunk",
"--dist-prefill-window",
"--dist-activation-bits",
"--dist-replay-check",
"--debug",
"-h, --help",
];
#[test]
fn help_contains_each_claimed_c_flag() {
let help = help_text("ds4-rs");
for flag in CLAIMED_C_CLI_FLAGS {
assert!(
help.contains(flag),
"ds4-rs help missing claimed C flag {flag}"
);
}
}
#[test]
fn dump_tokens_missing_prompt_errors_like_c() {
let parsed = parse_args(args(&["--dump-tokens"])).unwrap();
assert_eq!(
run_dump_tokens_cli(&parsed).unwrap_err(),
"ds4: --dump-tokens requires -p or --prompt-file"
);
}
#[test]
fn dump_tokens_path_does_not_call_eval() {
let parsed = parse_args(args(&["--dump-tokens", "-p", "hi"])).unwrap();
assert_eq!(eval_plan(&parsed), EvalPlan::Skip);
assert_eq!(
format_dump_tokens(&[7, 8], &[(7, b"ab".as_slice()), (8, b"cd".as_slice())]),
b"[7, 8]\n 7 ab\n 8 cd\n"
);
}
}
//! Shadow CLI host. Calls the same C inference core through `ds4-core`.
pub mod agent;
pub mod bench;
mod conversation;
pub mod repl;
pub mod session_exec;
pub mod session_snapshot;
mod token_printer;
pub mod worker_hello;
pub mod worker_run;
use token_printer::TokenPrinter;
use ds4_core::{Backend, ModelOpenOption};
pub(crate) fn distributed_config(opt: &ds4_dist::Options) -> Option<ds4_core::DistributedConfig> {
let role = match opt.role {
ds4_dist::Role::None => return None,
ds4_dist::Role::Coordinator => ds4_core::DistributedRole::Coordinator,
ds4_dist::Role::Worker => ds4_core::DistributedRole::Worker,
};
Some(ds4_core::DistributedConfig {
role,
layer_start: opt.layers.start,
layer_end: opt.layers.end,
has_output: opt.layers.has_output,
listen_host: opt.listen_host.clone(),
listen_port: opt.listen_port,
coordinator_host: opt.coordinator_host.clone(),
coordinator_port: opt.coordinator_port,
prefill_chunk: opt.prefill_chunk,
prefill_window: opt.prefill_window,
activation_bits: opt.activation_bits,
replay_check: opt.replay_check,
debug: opt.debug,
})
}
pub struct ShadowArgs {
pub model: Option<String>,
pub mtp: Option<String>,
pub mtp_draft: i32,
pub mtp_margin: f32,
pub dspark: Option<String>,
pub gen_ids: Option<String>,
pub engram_dir: Option<String>,
pub zchain: Option<String>,
pub posttrain: Option<String>,
/// The `--vision-gguf` sidecar (the ds41 tower's weights); also the
/// server's spelling, one native open slot for every family.
pub vision: Option<String>,
/// `--image` (repeatable, the engine's parser shape); only the golden
/// gate consumes it until the one-shot image request is ported.
pub images: Vec<String>,
/// `--vision-golden <VGLD>`: run the tower gate instead of generating.
pub vision_golden: Option<String>,
pub mem_budget_mb: Option<i32>,
pub no_engram: bool,
pub no_dspark: bool,
pub dspark_verify: i32,
pub no_graph: bool,
pub emit_trace: bool,
pub v41_prof: bool,
pub backend: Backend,
pub ctx: i32,
pub ctx_set: bool,
pub threads: i32,
pub tokens: Vec<i32>,
pub predict: i32,
pub n_predict: i32,
pub system: String,
pub nothink: bool,
pub prompt: Option<String>,
pub prompt_file: Option<String>,
pub dump_logprobs: Option<String>,
pub dump_logits: Option<String>,
pub dump_tokens: bool,
pub logprobs_top_k: i32,
pub temp: f32,
pub top_p: f32,
pub min_p: f32,
pub min_p_set: bool,
pub seed: u64,
pub dry_multiplier: f32,
pub dry_base: f32,
pub dry_allowed_length: i32,
pub lifecycle_only: bool,
pub identify: bool,
pub inventory: bool,
pub tokenize: bool,
pub tok_family: Option<String>,
pub tok_cmd: Option<String>,
pub tok_arg: Option<String>,
pub session_plan: bool,
pub session_cmd: Option<String>,
pub session_args: Vec<String>,
pub session_payload: bool,
pub payload_cmd: Option<String>,