Retire the Qwen AR pipeline
This commit is contained in:
1 parent
5773942c8f
commit
1de78fa3ef
22 files changed
+6429
-667
No files matched your search
@@ -29,6 +29,8 @@ mod hyper;
|
||||
mod indexing;
|
||||
#[cfg(test)]
|
||||
mod issue103_analysis;
|
||||
#[cfg(test)]
|
||||
mod issue104;
|
||||
mod linear;
|
||||
mod load;
|
||||
mod mlp;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
use super::array::{Array, Dtype};
|
||||
use super::execution::Throttle;
|
||||
use super::model::Output;
|
||||
use super::sampling::{SamplerConfig, lazy_sample, sample_from_logits};
|
||||
use super::ops;
|
||||
use super::sampling::{SamplerConfig, sample_from_logits};
|
||||
use super::stream::{Stream, Streams};
|
||||
use super::{ops, random};
|
||||
use crate::engine::Rng;
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -15,36 +15,21 @@ pub(super) struct Options<'a> {
|
||||
pub(super) max_tokens: usize,
|
||||
pub(super) seed: u64,
|
||||
pub(super) stop_tokens: &'a [i64],
|
||||
pub(super) pipeline: bool,
|
||||
pub(super) sync_eval: bool,
|
||||
pub(super) return_hidden: bool,
|
||||
pub(super) capture_final_state: bool,
|
||||
}
|
||||
|
||||
impl Options<'_> {
|
||||
pub(super) fn pipeline_eligible(&self, sampler: &SamplerConfig) -> bool {
|
||||
self.pipeline
|
||||
&& sampler.temperature > 0.0
|
||||
&& sampler.top_k > 1
|
||||
&& sampler.top_k < 4096
|
||||
&& sampler.presence_penalty == 0.0
|
||||
&& sampler.frequency_penalty == 0.0
|
||||
&& self.max_tokens > 2
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Decoded {
|
||||
pub(super) tokens: Vec<i64>,
|
||||
pub(super) final_logits: Option<Array>,
|
||||
pub(super) final_state_error: Option<String>,
|
||||
pub(super) pipeline: bool,
|
||||
pub(super) verify_calls: usize,
|
||||
pub(super) forward_seconds: f64,
|
||||
pub(super) eval_seconds: f64,
|
||||
pub(super) decode_seconds: f64,
|
||||
pub(super) elapsed_seconds: f64,
|
||||
cached_final_row: Option<Array>,
|
||||
}
|
||||
|
||||
pub(super) fn last_row(logits: &Array, stream: Stream) -> Result<Array, String> {
|
||||
@@ -65,14 +50,12 @@ fn token_array(token: i64) -> Result<Array, String> {
|
||||
)
|
||||
}
|
||||
|
||||
/// The model owner enables/disables pipeline mode around this call, including
|
||||
/// errors from the callback. Forward consumes resident token arrays unchanged.
|
||||
/// Every token uses the request PCG64 stream, matching classic generate_ar.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn decode(
|
||||
initial_logits: &Array,
|
||||
sampler: &SamplerConfig,
|
||||
options: &Options<'_>,
|
||||
pipeline_ready: bool,
|
||||
streams: &Streams,
|
||||
stream: Stream,
|
||||
throttle: &mut Throttle,
|
||||
@@ -83,7 +66,6 @@ pub(super) fn decode(
|
||||
let mut result = Decoded::default();
|
||||
let mut rng = Rng::new_qwen(options.seed);
|
||||
let mut logits = initial_logits.clone();
|
||||
let mut final_row = None;
|
||||
let stopped = |token: i64| options.stop_tokens.contains(&token);
|
||||
let mut commit = |tokens: &mut Vec<i64>, token| -> Result<(), String> {
|
||||
tokens.push(token);
|
||||
@@ -92,101 +74,50 @@ pub(super) fn decode(
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
if options.pipeline_eligible(sampler) && pipeline_ready {
|
||||
result.pipeline = true;
|
||||
let mut key = random::key(options.seed & 0x7fff_ffff)?;
|
||||
let mut counts = Vec::<(i64, i64)>::new();
|
||||
for step in 0..options.max_tokens {
|
||||
let row = ops::reshape(&logits, &[-1], stream)?;
|
||||
let token = sample_from_logits(&row, sampler, &mut rng, &[], &[], streams, stream)?.0;
|
||||
if !options.sync_eval && step > 0 {
|
||||
let started = Instant::now();
|
||||
ops::evaluate(streams, std::slice::from_ref(&row), stream, false)?;
|
||||
let elapsed = started.elapsed().as_secs_f64();
|
||||
result.eval_seconds += elapsed;
|
||||
result.decode_seconds += elapsed;
|
||||
}
|
||||
let token = sample_from_logits(&row, sampler, &mut rng, &counts, &[], streams, stream)?.0;
|
||||
commit(&mut result.tokens, token)?;
|
||||
if !stopped(token) {
|
||||
let mut lane_step = |token: &Array| -> Result<(Array, Array), String> {
|
||||
let (next, sub) = random::split(&key, stream)?;
|
||||
key = next;
|
||||
let out = forward(&ops::reshape(token, &[1, 1], stream)?, false)?;
|
||||
let row = last_row(
|
||||
out.logits.as_ref().ok_or("AR forward omitted logits")?,
|
||||
stream,
|
||||
)?;
|
||||
let next = lazy_sample(&ops::reshape(&row, &[-1], stream)?, sampler, &sub, stream)?;
|
||||
Ok((row, next))
|
||||
};
|
||||
let started = Instant::now();
|
||||
let first_power_started = throttle.begin();
|
||||
let (mut _row_lazy, mut token_lazy) = lane_step(&token_array(token)?)?;
|
||||
ops::evaluate(streams, std::slice::from_ref(&token_lazy), stream, true)?;
|
||||
result.forward_seconds += started.elapsed().as_secs_f64();
|
||||
throttle.pause(streams, stream, first_power_started)?;
|
||||
loop {
|
||||
let power_started = throttle.begin();
|
||||
let built = Instant::now();
|
||||
let (row_next, token_next) = lane_step(&token_lazy)?;
|
||||
ops::evaluate(streams, std::slice::from_ref(&token_next), stream, true)?;
|
||||
let build_elapsed = built.elapsed().as_secs_f64();
|
||||
let waited = Instant::now();
|
||||
let token = ops::read_i64(&token_lazy, streams, stream)?[0];
|
||||
let wait_elapsed = waited.elapsed().as_secs_f64();
|
||||
result.forward_seconds += build_elapsed;
|
||||
result.eval_seconds += wait_elapsed;
|
||||
result.decode_seconds += build_elapsed + wait_elapsed;
|
||||
result.verify_calls += 1;
|
||||
throttle.pause(streams, stream, power_started)?;
|
||||
commit(&mut result.tokens, token)?;
|
||||
if stopped(token) || result.tokens.len() >= options.max_tokens {
|
||||
final_row = Some(row_next);
|
||||
break;
|
||||
}
|
||||
_row_lazy = row_next;
|
||||
token_lazy = token_next;
|
||||
if sampler.presence_penalty != 0.0 || sampler.frequency_penalty != 0.0 {
|
||||
if let Some((_, count)) = counts.iter_mut().find(|(id, _)| *id == token) {
|
||||
*count += 1;
|
||||
} else {
|
||||
counts.push((token, 1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut counts = Vec::<(i64, i64)>::new();
|
||||
for step in 0..options.max_tokens {
|
||||
let row = ops::reshape(&logits, &[-1], stream)?;
|
||||
if !options.sync_eval && step > 0 {
|
||||
let started = Instant::now();
|
||||
ops::evaluate(streams, std::slice::from_ref(&row), stream, false)?;
|
||||
let elapsed = started.elapsed().as_secs_f64();
|
||||
result.eval_seconds += elapsed;
|
||||
result.decode_seconds += elapsed;
|
||||
}
|
||||
let token =
|
||||
sample_from_logits(&row, sampler, &mut rng, &counts, &[], streams, stream)?.0;
|
||||
commit(&mut result.tokens, token)?;
|
||||
if sampler.presence_penalty != 0.0 || sampler.frequency_penalty != 0.0 {
|
||||
if let Some((_, count)) = counts.iter_mut().find(|(id, _)| *id == token) {
|
||||
*count += 1;
|
||||
} else {
|
||||
counts.push((token, 1));
|
||||
}
|
||||
}
|
||||
if step + 1 >= options.max_tokens || stopped(token) {
|
||||
break;
|
||||
}
|
||||
let started = Instant::now();
|
||||
let power_started = throttle.begin();
|
||||
let output = forward(&token_array(token)?, options.return_hidden)?;
|
||||
result.forward_seconds += started.elapsed().as_secs_f64();
|
||||
let eval_started = Instant::now();
|
||||
let next = output.logits.ok_or("AR forward omitted logits")?;
|
||||
let mut roots = vec![next.clone()];
|
||||
roots.extend(output.hidden);
|
||||
ops::evaluate(streams, &roots, stream, !options.sync_eval)?;
|
||||
result.eval_seconds += eval_started.elapsed().as_secs_f64();
|
||||
result.decode_seconds += started.elapsed().as_secs_f64();
|
||||
result.verify_calls += 1;
|
||||
logits = last_row(&next, stream)?;
|
||||
throttle.pause(streams, stream, power_started)?;
|
||||
if step + 1 >= options.max_tokens || stopped(token) {
|
||||
break;
|
||||
}
|
||||
let started = Instant::now();
|
||||
let power_started = throttle.begin();
|
||||
let output = forward(&token_array(token)?, options.return_hidden)?;
|
||||
result.forward_seconds += started.elapsed().as_secs_f64();
|
||||
let eval_started = Instant::now();
|
||||
let next = output.logits.ok_or("AR forward omitted logits")?;
|
||||
let mut roots = vec![next.clone()];
|
||||
roots.extend(output.hidden);
|
||||
ops::evaluate(streams, &roots, stream, !options.sync_eval)?;
|
||||
result.eval_seconds += eval_started.elapsed().as_secs_f64();
|
||||
result.decode_seconds += started.elapsed().as_secs_f64();
|
||||
result.verify_calls += 1;
|
||||
logits = last_row(&next, stream)?;
|
||||
throttle.pause(streams, stream, power_started)?;
|
||||
}
|
||||
// Match generate_ar: response timing ends before session-bank tail capture.
|
||||
result.elapsed_seconds = started_all.elapsed().as_secs_f64();
|
||||
result.cached_final_row = final_row;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Called after the owner restores non-pipeline mode, as in generate_ar's
|
||||
/// finally block. Capture failures preserve a successfully finished response.
|
||||
/// Extend the cache through the last sampled token, as in generate_ar.
|
||||
/// Capture failures preserve a successfully finished response.
|
||||
pub(super) fn capture_final_state(
|
||||
result: &mut Decoded,
|
||||
options: &Options<'_>,
|
||||
@@ -194,20 +125,16 @@ pub(super) fn capture_final_state(
|
||||
stream: Stream,
|
||||
mut forward: impl FnMut(&Array, bool) -> Result<Output, String>,
|
||||
) {
|
||||
let final_row = result.cached_final_row.take();
|
||||
if options.capture_final_state
|
||||
&& let Some(&last) = result.tokens.last()
|
||||
{
|
||||
let capture = (|| {
|
||||
let row = match final_row {
|
||||
Some(row) => row,
|
||||
None => last_row(
|
||||
&forward(&token_array(last)?, false)?
|
||||
.logits
|
||||
.ok_or("AR tail omitted logits")?,
|
||||
stream,
|
||||
)?,
|
||||
};
|
||||
let row = last_row(
|
||||
&forward(&token_array(last)?, false)?
|
||||
.logits
|
||||
.ok_or("AR tail omitted logits")?,
|
||||
stream,
|
||||
)?;
|
||||
ops::evaluate(streams, std::slice::from_ref(&row), stream, false)?;
|
||||
Ok::<_, String>(row)
|
||||
})();
|
||||
@@ -235,11 +162,13 @@ fn mtplx_ar_driver_matches_reference() {
|
||||
.lines()
|
||||
.map(|line| serde_json::from_str::<serde_json::Value>(line).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(cases.len(), 480);
|
||||
assert_eq!(cases.len(), 1120);
|
||||
for (index, case) in cases.iter().enumerate() {
|
||||
eprintln!("AR driver reference {index}/{}", cases.len());
|
||||
let sampler = SamplerConfig {
|
||||
temperature: case["temperature"].as_f64().unwrap(),
|
||||
top_p: case["top_p"].as_f64().unwrap(),
|
||||
top_k: case["top_k"].as_i64().unwrap() as i32,
|
||||
presence_penalty: case["penalty"].as_f64().unwrap(),
|
||||
..Default::default()
|
||||
};
|
||||
@@ -253,7 +182,6 @@ fn mtplx_ar_driver_matches_reference() {
|
||||
max_tokens: case["limit"].as_u64().unwrap() as usize,
|
||||
seed: 4294967297,
|
||||
stop_tokens: &stops,
|
||||
pipeline: case["pipeline"].as_bool().unwrap(),
|
||||
sync_eval: !case["asynchronous"].as_bool().unwrap(),
|
||||
return_hidden: false,
|
||||
capture_final_state: case["capture"].as_bool().unwrap(),
|
||||
@@ -278,7 +206,6 @@ fn mtplx_ar_driver_matches_reference() {
|
||||
&row,
|
||||
&sampler,
|
||||
&options,
|
||||
true,
|
||||
&streams,
|
||||
stream,
|
||||
&mut Throttle::new(100).unwrap(),
|
||||
@@ -327,11 +254,6 @@ fn mtplx_ar_driver_matches_reference() {
|
||||
case["final"],
|
||||
"final cache row case {index}"
|
||||
);
|
||||
assert_eq!(
|
||||
result.pipeline,
|
||||
!case["modes"].as_array().unwrap().is_empty(),
|
||||
"pipeline admission case {index}"
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::json!(result.verify_calls),
|
||||
case["verify_calls"],
|
||||
|
||||
@@ -674,10 +674,6 @@ fn mtplx_installed_module_binding_matches_reference() {
|
||||
);
|
||||
}
|
||||
assert_eq!(serde_json::json!(tables), sidecar_case["tables"]);
|
||||
assert_eq!(
|
||||
model.set_ar_pipeline_mode(true),
|
||||
sidecar_case["ar_ready"].as_bool().unwrap()
|
||||
);
|
||||
drop(model);
|
||||
assert!(
|
||||
bank.materialize(&["missing.weight".into()], &streams, gpu)
|
||||
|
||||
@@ -532,7 +532,6 @@ fn mtplx_decoder_connected_attention_and_moe_advance_caches() {
|
||||
CompilePolicy::from_values(Some("1"), None),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(model.set_ar_pipeline_mode(true), !with_ple);
|
||||
let mut caches = model.make_cache(4);
|
||||
let mut offset = 0;
|
||||
for (rows, keep, emit, capture) in [
|
||||
|
||||
@@ -827,15 +827,10 @@ impl Execution {
|
||||
finish_decode: impl FnOnce(f64) -> Result<(), String>,
|
||||
) -> Result<ar::Decoded, String> {
|
||||
let graph_options = self.graph_options(false, false)?;
|
||||
let lane = options.pipeline_eligible(sampler)
|
||||
&& self
|
||||
.model
|
||||
.with_dependent_mut(|_, model| model.set_ar_pipeline_mode(true));
|
||||
let mut result = ar::decode(
|
||||
logits,
|
||||
sampler,
|
||||
options,
|
||||
lane,
|
||||
&self.streams,
|
||||
self.stream,
|
||||
&mut self.decode_throttle,
|
||||
@@ -859,10 +854,6 @@ impl Execution {
|
||||
},
|
||||
emit,
|
||||
);
|
||||
if lane {
|
||||
self.model
|
||||
.with_dependent_mut(|_, model| model.set_ar_pipeline_mode(false));
|
||||
}
|
||||
if let Ok(decoded) = &mut result {
|
||||
finish_decode(decoded.elapsed_seconds)?;
|
||||
ar::capture_final_state(
|
||||
@@ -1185,7 +1176,6 @@ fn mtplx_full_installed_ar_matches_reference() {
|
||||
max_tokens: usize::MAX,
|
||||
seed: end["seed"].as_u64().unwrap(),
|
||||
stop_tokens: &stops,
|
||||
pipeline: flag("MTPLX_AR_PIPELINE"),
|
||||
sync_eval: !flag("MTPLX_ASYNC_AR")
|
||||
|| !std::env::var("MTPLX_EVAL_AUDIT")
|
||||
.unwrap_or_default()
|
||||
@@ -1222,7 +1212,7 @@ fn mtplx_full_installed_ar_matches_reference() {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({"event":"canonical_ar_end", "tokens":result.tokens, "verify_calls":result.verify_calls,
|
||||
"decode_elapsed_seconds":result.elapsed_seconds, "prefill_seconds":prepared.eval_seconds, "finish_reason":"stop", "pipeline":result.pipeline})
|
||||
"decode_elapsed_seconds":result.elapsed_seconds, "prefill_seconds":prepared.eval_seconds, "finish_reason":"stop"})
|
||||
);
|
||||
phase.store(EvaluationPhase::Finishing as u8, Ordering::Relaxed);
|
||||
}));
|
||||
@@ -1461,7 +1451,6 @@ fn mtplx_full_installed_ping_forward_matches_reference() {
|
||||
max_tokens: usize::MAX,
|
||||
seed: 0,
|
||||
stop_tokens: &stops,
|
||||
pipeline: false,
|
||||
sync_eval: true,
|
||||
return_hidden: false,
|
||||
capture_final_state: false,
|
||||
@@ -1486,7 +1475,6 @@ fn mtplx_full_installed_ping_forward_matches_reference() {
|
||||
emitted.push(token as i64);
|
||||
assert_eq!(ar_result.tokens, emitted);
|
||||
assert_eq!(ar_result.verify_calls, generated.len());
|
||||
assert!(!ar_result.pipeline);
|
||||
} else {
|
||||
assert!(
|
||||
!receipts.iter().any(|r| r["event"] == "decode_reference"),
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
//! Full installed-model AR regression; run serially under test-supervisor.
|
||||
use super::ar;
|
||||
use super::array::Array;
|
||||
use super::execution::Execution;
|
||||
use super::sampling::SamplerConfig;
|
||||
use super::session_cache::{DecodeOptions, SessionSnapshot, State, TurnOptions};
|
||||
|
||||
fn fingerprint(execution: &Execution, array: &Array) -> serde_json::Value {
|
||||
let values = execution.values_f32(array).unwrap();
|
||||
assert!(values.iter().all(|v| v.is_finite()));
|
||||
serde_json::json!({"shape":array.layout().shape(), "sha256":
|
||||
super::super::qwen_mtplx_tests::hex_digest(&values.iter().flat_map(|v|v.to_le_bytes()).collect::<Vec<_>>())})
|
||||
}
|
||||
|
||||
fn state(execution: &Execution, saved: &SessionSnapshot) -> serde_json::Value {
|
||||
let states = saved
|
||||
.trunk
|
||||
.iter()
|
||||
.map(|s| {
|
||||
s.as_ref().map(|s| {
|
||||
let arrays = match s {
|
||||
State::Recurrent(arrays) => arrays.as_slice(),
|
||||
State::Qsa(arrays) => arrays.as_slice(),
|
||||
};
|
||||
arrays
|
||||
.iter()
|
||||
.map(|a| a.as_ref().map(|a| fingerprint(execution, a)))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
serde_json::json!({"ids":saved.token_ids,"logits":fingerprint(execution,&saved.logits),"states":states})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "full pinned Bare-Speed model; external memory guard required"]
|
||||
fn issue104_installed_ar_stream_and_state() {
|
||||
let root = std::path::PathBuf::from(std::env::var_os("DS4_QWEN38_ARTIFACTS").unwrap());
|
||||
eprintln!(
|
||||
"#104 loading unchanged Bare-Speed model; resident={}",
|
||||
super::ngram::resident_policy()
|
||||
);
|
||||
let mut execution = Execution::load(&root, false, false, 100).unwrap();
|
||||
let tables = execution
|
||||
.model
|
||||
.borrow_dependent()
|
||||
.layers
|
||||
.iter()
|
||||
.filter_map(|layer| {
|
||||
layer
|
||||
.ple
|
||||
.as_ref()
|
||||
.map(|ple| ple.embedding.table.resident.is_some())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert!(!tables.is_empty());
|
||||
assert!(
|
||||
tables.iter().all(|r| *r == super::ngram::resident_policy()),
|
||||
"full residency must actually attach"
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({"event":"issue104_environment","resident_tables":tables,"mtp":false})
|
||||
);
|
||||
let ids = [9707, 11, 1075];
|
||||
let prepared = execution
|
||||
.prefill_prompt(
|
||||
&ids,
|
||||
&super::prefill::Options {
|
||||
chunk_size: Some(2048),
|
||||
final_logits_only: true,
|
||||
external_cache_only: false,
|
||||
external_emit_logits: true,
|
||||
return_hidden: false,
|
||||
},
|
||||
|_| Ok(()),
|
||||
)
|
||||
.unwrap();
|
||||
let baseline = execution
|
||||
.capture_session(&ids, &prepared.logits, None, false, false)
|
||||
.unwrap();
|
||||
let mut records = Vec::new();
|
||||
let mut warm_records = Vec::<serde_json::Value>::new();
|
||||
for temperature in [0.6, 1.0] {
|
||||
let sampler = SamplerConfig {
|
||||
temperature,
|
||||
..Default::default()
|
||||
};
|
||||
let mut expected = None;
|
||||
for asynchronous in [false, true] {
|
||||
let (logits, _, reused) = execution.restore_session(&baseline, &ids).unwrap();
|
||||
assert_eq!(reused, ids.len());
|
||||
let mut emitted = Vec::new();
|
||||
let out = execution
|
||||
.decode_ar(
|
||||
&logits,
|
||||
&sampler,
|
||||
&ar::Options {
|
||||
max_tokens: 8,
|
||||
seed: 0,
|
||||
stop_tokens: &[],
|
||||
sync_eval: !asynchronous,
|
||||
return_hidden: false,
|
||||
capture_final_state: true,
|
||||
},
|
||||
|id| {
|
||||
emitted.push(id);
|
||||
eprintln!("#104 t={temperature} async={asynchronous}: token {id}");
|
||||
Ok(())
|
||||
},
|
||||
|_| Ok(()),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(out.final_state_error.is_none());
|
||||
for layer in &execution.model.borrow_dependent().layers {
|
||||
if let Some(ple) = &layer.ple {
|
||||
assert!(!ple.embedding.stage_disabled);
|
||||
assert!(!ple.embedding.table.prefer_lazy);
|
||||
}
|
||||
}
|
||||
assert_eq!(out.tokens, emitted);
|
||||
let mut key = ids.to_vec();
|
||||
key.extend(out.tokens.iter().map(|id| *id as i32));
|
||||
let saved = execution
|
||||
.capture_session(&key, out.final_logits.as_ref().unwrap(), None, false, false)
|
||||
.unwrap();
|
||||
let value = state(&execution, &saved);
|
||||
if !asynchronous {
|
||||
expected = Some(value.clone());
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({"event":"issue104_ar","temperature":temperature,"asynchronous":asynchronous,"tokens":out.tokens,"state":value,"seconds":out.elapsed_seconds})
|
||||
);
|
||||
assert_eq!(value, expected.clone().unwrap());
|
||||
let mut prompt = key.clone();
|
||||
prompt.push(1075);
|
||||
let options = TurnOptions {
|
||||
prefill: super::prefill::Options {
|
||||
chunk_size: Some(2048),
|
||||
final_logits_only: true,
|
||||
external_cache_only: false,
|
||||
external_emit_logits: true,
|
||||
return_hidden: false,
|
||||
},
|
||||
decode: DecodeOptions::Ar(ar::Options {
|
||||
max_tokens: 8,
|
||||
seed: 0,
|
||||
stop_tokens: &[],
|
||||
sync_eval: !asynchronous,
|
||||
return_hidden: false,
|
||||
capture_final_state: true,
|
||||
}),
|
||||
sampler,
|
||||
draft_sampler: sampler,
|
||||
lazy_kv: false,
|
||||
store_min_suffix: None,
|
||||
fused_max: 512,
|
||||
};
|
||||
// Cancel after forwards have advanced the live cache, then retry
|
||||
// from the committed bank entry. The saved state must survive.
|
||||
let mut callbacks = 0;
|
||||
let cancelled = execution.session_turn(
|
||||
&prompt,
|
||||
Some(&saved),
|
||||
&options,
|
||||
|_| Ok(()),
|
||||
|_| {
|
||||
callbacks += 1;
|
||||
if callbacks == 3 {
|
||||
Err("cancelled".into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
|_| Ok(()),
|
||||
);
|
||||
assert!(matches!(cancelled, Err(ref error) if error == "cancelled"));
|
||||
assert_eq!(state(&execution, &saved), value);
|
||||
let warm = execution
|
||||
.session_turn(
|
||||
&prompt,
|
||||
Some(&saved),
|
||||
&options,
|
||||
|_| Ok(()),
|
||||
|_| Ok(()),
|
||||
|_| Ok(()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(warm.cached_tokens, key.len());
|
||||
assert!(warm.final_snapshot_error.is_none());
|
||||
let warm_state = state(&execution, warm.snapshot.as_ref().unwrap());
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({"event":"issue104_warm", "temperature":temperature,
|
||||
"asynchronous":asynchronous,"state":warm_state})
|
||||
);
|
||||
if asynchronous {
|
||||
assert_eq!(warm_state, warm_records.last().unwrap()["state"]);
|
||||
} else {
|
||||
warm_records
|
||||
.push(serde_json::json!({"temperature":temperature,"state":warm_state}));
|
||||
}
|
||||
if !asynchronous {
|
||||
records.push(serde_json::json!({"temperature":temperature,"state":value}));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(path) = std::env::var("DS4_ISSUE104_BASELINE") {
|
||||
let expected: serde_json::Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap();
|
||||
assert_eq!(serde_json::json!(records), expected);
|
||||
}
|
||||
let oracle: serde_json::Value = serde_json::from_str(include_str!(
|
||||
"../../../../tests/fixtures/issue104-ar-model.json"
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(serde_json::json!(records), oracle["cold"]);
|
||||
assert_eq!(serde_json::json!(warm_records), oracle["warm"]);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({"event":"issue104_classic_baseline","records":records})
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,6 @@ use std::ops::Range;
|
||||
pub(super) struct CompilePolicy {
|
||||
explicit_off: bool,
|
||||
environment: bool,
|
||||
lane: bool,
|
||||
}
|
||||
|
||||
impl CompilePolicy {
|
||||
@@ -29,7 +28,6 @@ impl CompilePolicy {
|
||||
Self {
|
||||
explicit_off,
|
||||
environment: !explicit_off && [gdn, family].into_iter().flatten().any(truthy),
|
||||
lane: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +40,7 @@ impl CompilePolicy {
|
||||
}
|
||||
|
||||
fn enabled(self) -> bool {
|
||||
!self.explicit_off && (self.environment || self.lane)
|
||||
!self.explicit_off && self.environment
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,23 +303,6 @@ impl<'a> TextModel<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn set_ar_pipeline_mode(&mut self, enabled: bool) -> bool {
|
||||
let mut ready = true;
|
||||
for layer in &mut self.layers {
|
||||
let Some(ple) = &mut layer.ple else { continue };
|
||||
if enabled && ple.embedding.table.resident.is_none() {
|
||||
ready = false;
|
||||
continue;
|
||||
}
|
||||
ple.embedding.stage_disabled = enabled;
|
||||
ple.embedding.table.prefer_lazy = enabled;
|
||||
}
|
||||
if ready {
|
||||
self.policy.lane = enabled && !self.policy.explicit_off;
|
||||
}
|
||||
ready
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), expect(dead_code, reason = "Reference diagnostic API"))]
|
||||
pub(super) fn clear_verify_capture(cache: &mut [Option<Cache>]) {
|
||||
for cache in cache.iter_mut().flatten() {
|
||||
@@ -777,19 +758,16 @@ fn mtplx_model_compile_policy_and_run_boundaries() {
|
||||
assert!(CompilePolicy::from_values(Some(on), None).enabled());
|
||||
assert!(CompilePolicy::from_values(None, Some(on)).enabled());
|
||||
for off in ["0", "", "false", "off", "anything"] {
|
||||
for mut policy in [
|
||||
for policy in [
|
||||
CompilePolicy::from_values(Some(on), Some(off)),
|
||||
CompilePolicy::from_values(Some(off), Some(on)),
|
||||
] {
|
||||
policy.lane = true;
|
||||
assert!(!policy.enabled());
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut automatic = CompilePolicy::from_values(None, None);
|
||||
let automatic = CompilePolicy::from_values(None, None);
|
||||
assert!(!automatic.enabled());
|
||||
automatic.lane = true;
|
||||
assert!(automatic.enabled());
|
||||
let environment = CompilePolicy::from_env();
|
||||
assert_eq!(
|
||||
environment.enabled(),
|
||||
|
||||
@@ -127,7 +127,7 @@ impl Table {
|
||||
self.resident = Some(resident);
|
||||
self.prefer_lazy = false;
|
||||
println!(
|
||||
"[qwen4_exp] ngram table resident: {:.1}G in {:.1}s (pipelined-AR lane armed)",
|
||||
"[qwen4_exp] ngram table resident: {:.1}G in {:.1}s",
|
||||
nbytes as f64 / (1u64 << 30) as f64,
|
||||
started.elapsed().as_secs_f64()
|
||||
);
|
||||
|
||||
@@ -53,7 +53,6 @@ pub(super) fn server_overrides(mtp: bool, get: impl Fn(&str) -> Option<String>)
|
||||
result.insert("MTPLX_SKIP_VERIFY_SNAPSHOT", "0");
|
||||
}
|
||||
for key in [
|
||||
"MTPLX_AR_PIPELINE",
|
||||
"MTPLX_FAMILY_CAPTURE_COMMIT",
|
||||
"MTPLX_FUSED_HC_V3",
|
||||
"MTPLX_FUSED_GDN_INPROJ",
|
||||
@@ -137,7 +136,6 @@ pub(super) fn turn_options(
|
||||
max_tokens: usize::MAX,
|
||||
seed: 0,
|
||||
stop_tokens: &[],
|
||||
pipeline: flag("MTPLX_AR_PIPELINE"),
|
||||
sync_eval: !flag("MTPLX_ASYNC_AR")
|
||||
|| get("MTPLX_EVAL_AUDIT").is_some_and(|s| !s.is_empty()),
|
||||
return_hidden: false,
|
||||
@@ -182,6 +180,42 @@ pub(super) fn turn_options(
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retired_ar_pipeline_flag_cannot_change_server_or_turn_policy() {
|
||||
let mut settings = crate::settings::effective_settings(
|
||||
crate::model::ModelChoice::Qwen38FlashNext,
|
||||
&crate::settings::GenerationPreferences::default(),
|
||||
&crate::settings::RuntimePreferences::default(),
|
||||
std::path::Path::new("/unused-policy-models"),
|
||||
)
|
||||
.unwrap()
|
||||
.engine;
|
||||
settings.speculative.glm_mtp = false;
|
||||
for legacy in [None, Some("0"), Some("1")] {
|
||||
let lookup = |key: &str| {
|
||||
(key == "MTPLX_AR_PIPELINE")
|
||||
.then_some(legacy)
|
||||
.flatten()
|
||||
.map(str::to_owned)
|
||||
};
|
||||
let overrides = server_overrides(false, lookup);
|
||||
assert_eq!(overrides, server_overrides(false, |_| None));
|
||||
let (options, _) = turn_options(&settings, |key| {
|
||||
assert_ne!(key, "MTPLX_AR_PIPELINE", "retired option was read");
|
||||
overrides
|
||||
.get(key)
|
||||
.map(|s| (*s).to_owned())
|
||||
.or_else(|| lookup(key))
|
||||
})
|
||||
.unwrap();
|
||||
let DecodeOptions::Ar(ar) = options.decode else {
|
||||
panic!("AR request changed modes");
|
||||
};
|
||||
assert!(ar.sync_eval && ar.capture_final_state);
|
||||
assert!(!ar.return_hidden);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires DS4_QWEN38_POLICY_REFERENCE from pinned MTPLX; CPU-only"]
|
||||
fn qwen_server_policy_matches_original_functions() {
|
||||
@@ -198,6 +232,23 @@ fn qwen_server_policy_matches_original_functions() {
|
||||
let mtp = case["mtp"].as_bool().unwrap();
|
||||
let lookup = |key: &str| case["environment"][key].as_str().map(str::to_owned);
|
||||
let overrides = server_overrides(mtp, lookup);
|
||||
if case["server_only"] == true {
|
||||
// This scoped comparison owns AR retirement and the existing
|
||||
// defaults. New upstream features are outside #104; retain the
|
||||
// complete legacy policy comparison below for its own oracle.
|
||||
assert!(
|
||||
!case["overrides"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.contains_key("MTPLX_AR_PIPELINE")
|
||||
);
|
||||
assert!(!overrides.contains_key("MTPLX_AR_PIPELINE"));
|
||||
for (key, value) in &overrides {
|
||||
assert_eq!(case["overrides"][key], *value, "default {key} case {count}");
|
||||
}
|
||||
count += 1;
|
||||
continue;
|
||||
}
|
||||
assert_eq!(
|
||||
serde_json::to_value(&overrides).unwrap(),
|
||||
case["overrides"],
|
||||
@@ -253,7 +304,6 @@ fn qwen_server_policy_matches_original_functions() {
|
||||
match actual.decode {
|
||||
DecodeOptions::Ar(o) => {
|
||||
assert!(!mtp);
|
||||
assert_eq!(o.pipeline, expected["pipeline"]);
|
||||
assert_eq!(o.sync_eval, expected["sync_eval"]);
|
||||
}
|
||||
DecodeOptions::Mtp(o) => {
|
||||
|
||||
@@ -143,6 +143,7 @@ pub(super) fn scalar(value: f32) -> Result<Array, String> {
|
||||
|
||||
/// categorical(sv[None], key): the pinned runtime selects inverse CDF for
|
||||
/// one distribution with output shape [1], not the Gumbel-max route.
|
||||
#[cfg(test)]
|
||||
pub(super) fn categorical_row(
|
||||
logits: &Array,
|
||||
key: &Array,
|
||||
|
||||
@@ -284,7 +284,6 @@ fn qwen_request_settings_override_backend_policy() {
|
||||
max_tokens: 1,
|
||||
seed: 1,
|
||||
stop_tokens: &[],
|
||||
pipeline: true,
|
||||
sync_eval: true,
|
||||
return_hidden: false,
|
||||
capture_final_state: true,
|
||||
|
||||
@@ -150,7 +150,9 @@ fn order_bounded_support(
|
||||
|
||||
/// generation._mx_lazy_sample. Keep selection in the original model dtype,
|
||||
/// promote only the selected k logits, and return an unevaluated scalar token.
|
||||
/// The AR lane owns key splitting; neither this graph nor its RNG reads back.
|
||||
/// Historical diagnostic only: reproduces #104 and the keyed RNG fixtures.
|
||||
/// Production AR uses sample_from_logits for every token.
|
||||
#[cfg(test)]
|
||||
pub(super) fn lazy_sample(
|
||||
row: &Array,
|
||||
config: &SamplerConfig,
|
||||
@@ -299,7 +301,6 @@ pub(super) fn distribution_from_logits(
|
||||
|
||||
/// The ordinary generation._sample_from_logits path. Greedy evaluates model
|
||||
/// logits before constructing/evaluating ArgMax and never advances PCG64.
|
||||
/// The resident pipelined keyed-device sampler is a separate reference lane.
|
||||
pub(super) fn sample_from_logits(
|
||||
logits: &Array,
|
||||
config: &SamplerConfig,
|
||||
|
||||
Reference in new issue
Block a user