use anyhow::{Context, Result};
use std::path::PathBuf;
const DEFAULT_DRAFTER_SNAPSHOT: &str =
"models--z-lab--gemma-4-26B-A4B-it-DFlash/snapshots/77d4202772dfe50b2396ec7bac9cfffc7b9e7057";
fn resolve_drafter_path() -> Result<PathBuf> {
if let Ok(p) = std::env::var("HF2Q_DFLASH_DRAFTER_PATH") {
return Ok(PathBuf::from(p));
}
let home = std::env::var("HOME").context("HOME env var not set")?;
Ok(PathBuf::from(format!(
"{home}/.cache/huggingface/hub/{DEFAULT_DRAFTER_SNAPSHOT}"
)))
}
fn resolve_block_size() -> Result<u32> {
match std::env::var("HF2Q_DFLASH_BLOCK_SIZE") {
Err(_) => Ok(8),
Ok(s) => {
let n: u32 = s
.parse()
.with_context(|| format!("HF2Q_DFLASH_BLOCK_SIZE must be integer; got {s:?}"))?;
anyhow::ensure!(n >= 2, "HF2Q_DFLASH_BLOCK_SIZE must be ≥ 2; got {n}");
Ok(n)
}
}
}
pub fn try_dispatch_dflash_spec_decode(
target: &mut crate::inference::models::gemma4::MlxModelWeights,
prompt_tokens: &[u32],
max_new_tokens: usize,
eos_token_ids: &[u32],
ignore_eos: bool,
tokenizer: &tokenizers::Tokenizer,
gpu: &mut crate::serve::gpu::GpuContext,
) -> Result<Option<()>> {
if std::env::var("HF2Q_SPEC_DFLASH").as_deref() != Ok("1") {
return Ok(None);
}
let xlen_sdpa = std::env::var("HF2Q_DFLASH_XLEN_SDPA").as_deref() == Ok("1");
let full_f16_kv = std::env::var("HF2Q_FULL_F16_KV")
.ok()
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false);
if xlen_sdpa && !full_f16_kv {
anyhow::bail!(
"HF2Q_DFLASH_XLEN_SDPA=1 requires HF2Q_FULL_F16_KV=1 (xlen cross-length SDPA \
path needs F16 V cache; default TQ-HB 8-bit V quantization is incompatible). \
Set both env vars, or unset HF2Q_DFLASH_XLEN_SDPA to fall back to Option C \
(re-prefill from start_pos=0 each round, slower but doesn't require F16 V).",
);
}
if xlen_sdpa {
eprintln!(
"[HF2Q_SPEC_DFLASH=1 + HF2Q_DFLASH_XLEN_SDPA=1] loading DFlash drafter — Option A \
(cross-length SDPA), byte-identical to base for ~135 tokens then diverges via argmax flip at temp=0 greedy, 1.62× over Option C \
on Gemma but still 0.40× of base generation (research-quality)"
);
} else {
eprintln!(
"[HF2Q_SPEC_DFLASH=1] loading DFlash drafter — Option C re-prefill, slower than \
baseline + diverges from base autoregressive at temp=0; set HF2Q_DFLASH_XLEN_SDPA=1 \
+ HF2Q_FULL_F16_KV=1 for Option A (1.62× faster + byte-identical to base for ~135 tokens then diverges)"
);
}
use crate::inference::spec_decode::dflash::{
config::DFlashConfig,
kv_cache::DFlashKvCache,
orchestrator::dispatch_dflash_generate,
tensors::DFlashModelTensors,
weights::{DFlashWeights, DFlashWeightsFile},
};
let drafter_dir = resolve_drafter_path()?;
if !drafter_dir.is_dir() {
anyhow::bail!(
"HF2Q_SPEC_DFLASH=1 but drafter dir {} does not exist. \
Set HF2Q_DFLASH_DRAFTER_PATH or fetch \
z-lab/gemma-4-26B-A4B-it-DFlash from HuggingFace first.",
drafter_dir.display(),
);
}
let cfg_path = drafter_dir.join("config.json");
let weights_path = drafter_dir.join("model.safetensors");
for p in [&cfg_path, &weights_path] {
anyhow::ensure!(
p.exists(),
"DFlash drafter artifact missing: {}",
p.display()
);
}
let effective_eos: &[u32] = if ignore_eos { &[] } else { eos_token_ids };
let block_size = resolve_block_size()?;
let t_load = std::time::Instant::now();
let drafter_cfg =
DFlashConfig::from_json_path(&cfg_path).context("parse DFlash drafter config.json")?;
let drafter_file =
DFlashWeightsFile::open(&weights_path).context("open DFlash drafter safetensors")?;
let drafter_weights = DFlashWeights::load(drafter_file.bytes(), &drafter_cfg)
.context("validate + load DFlash drafter weights")?;
let drafter_tensors = {
let (exec, _reg) = gpu.split();
DFlashModelTensors::upload(exec.device(), &drafter_cfg, &drafter_weights)
.context("upload DFlash drafter weights to GPU")?
};
let drafter_cache_cap = (prompt_tokens.len() + max_new_tokens + 32).max(2048) as u32;
let mut drafter_cache = {
let (exec, _reg) = gpu.split();
DFlashKvCache::new(exec.device(), &drafter_cfg, drafter_cache_cap)
.context("allocate DFlash drafter KV cache")?
};
eprintln!(
"[HF2Q_SPEC_DFLASH] drafter loaded in {:.2}s (config={}, cache_cap={drafter_cache_cap})",
t_load.elapsed().as_secs_f64(),
cfg_path.display(),
);
let t_gen = std::time::Instant::now();
let output_tokens = dispatch_dflash_generate(
target,
&drafter_tensors,
&mut drafter_cache,
&drafter_cfg,
prompt_tokens,
max_new_tokens,
block_size,
effective_eos,
gpu,
)
.context("dispatch_dflash_generate")?;
let gen_elapsed = t_gen.elapsed();
let new_tokens = &output_tokens[prompt_tokens.len()..];
let decoded = tokenizer
.decode(new_tokens, false)
.unwrap_or_else(|e| format!("<decode failed: {e}>"));
println!("{decoded}");
eprintln!(
"[HF2Q_SPEC_DFLASH] {} new tokens in {:.2}s ({:.1} tok/s)",
new_tokens.len(),
gen_elapsed.as_secs_f64(),
new_tokens.len() as f64 / gen_elapsed.as_secs_f64().max(1e-6),
);
Ok(Some(()))
}
const DEFAULT_QWEN35_DRAFTER_DIR: &str =
"/opt/hf2q/models/dflash-drafters/z-lab__Qwen3.6-27B-DFlash";
fn resolve_qwen35_drafter_path() -> Result<PathBuf> {
if let Ok(p) = std::env::var("HF2Q_DFLASH_DRAFTER_PATH") {
return Ok(PathBuf::from(p));
}
Ok(PathBuf::from(DEFAULT_QWEN35_DRAFTER_DIR))
}
pub fn try_dispatch_qwen35_dflash_spec_decode(
model: &mut crate::inference::models::qwen35::model::Qwen35Model,
prompt_tokens: &[u32],
max_new_tokens: usize,
eos_token_ids: &[u32],
ignore_eos: bool,
tokenizer: &tokenizers::Tokenizer,
) -> Result<Option<()>> {
if std::env::var("HF2Q_SPEC_DFLASH").as_deref() != Ok("1") {
return Ok(None);
}
eprintln!(
"[HF2Q_SPEC_DFLASH=1 qwen35] WARNING: empirical bench (HEAD 334008e9 2026-05-22, \
128 tok 3-rep paired) — DFlash is research-quality on Qwen35: 27B DFlash \
23.17 t/s = 0.77x of MTP K=1 greedy (was 0.62x pre-task #95); 35B-A3B DFlash \
42.7 t/s = 0.31x of base (136.1) or 0.43x of MTP K=1 BATCHED (98.6). Output \
is coherence-degraded at longer lengths (27B Fibonacci 128-tok becomes \
garbled `a, b = a, b = ...` + hits EOS + loops into chat markers; 35B-A3B \
shows duplicated lines). Row-N kernel divergence vs single-token decode. \
For production use HF2Q_SPEC_DECODE=1 --temperature 0 (code-gen 1.37x base) \
or 0.5 (essay 1.26x base)."
);
use crate::inference::spec_decode::dflash::{
config::DFlashConfig,
kv_cache::DFlashKvCache,
qwen35_orchestrator::dispatch_qwen35_dflash_generate,
qwen35_target::Qwen35DFlashTarget,
tensors::DFlashModelTensors,
weights::{DFlashWeights, DFlashWeightsFile},
};
let drafter_dir = resolve_qwen35_drafter_path()?;
if !drafter_dir.is_dir() {
anyhow::bail!(
"HF2Q_SPEC_DFLASH=1 (qwen35 path) but drafter dir {} does not exist. \
Set HF2Q_DFLASH_DRAFTER_PATH or fetch \
z-lab/Qwen3.6-27B-DFlash from HuggingFace first.",
drafter_dir.display(),
);
}
let cfg_path = drafter_dir.join("config.json");
let weights_path = drafter_dir.join("model.safetensors");
for p in [&cfg_path, &weights_path] {
anyhow::ensure!(
p.exists(),
"DFlash drafter artifact missing: {}",
p.display()
);
}
let effective_eos: &[u32] = if ignore_eos { &[] } else { eos_token_ids };
let t_load = std::time::Instant::now();
let drafter_cfg = DFlashConfig::from_json_path(&cfg_path)
.context("parse Qwen35 DFlash drafter config.json")?;
let block_size: u32 = match std::env::var("HF2Q_DFLASH_BLOCK_SIZE") {
Ok(s) => {
let n: u32 = s
.parse()
.with_context(|| format!("HF2Q_DFLASH_BLOCK_SIZE must be integer; got {s:?}"))?;
anyhow::ensure!(n >= 2, "HF2Q_DFLASH_BLOCK_SIZE must be >= 2; got {n}");
n
}
Err(_) => {
let bs = drafter_cfg.block_size;
anyhow::ensure!(
bs >= 2 && bs <= u32::MAX as usize,
"drafter_cfg.block_size out of range: {bs}"
);
bs as u32
}
};
let drafter_file =
DFlashWeightsFile::open(&weights_path).context("open Qwen35 DFlash drafter safetensors")?;
let drafter_weights = DFlashWeights::load(drafter_file.bytes(), &drafter_cfg)
.context("validate + load Qwen35 DFlash drafter weights")?;
model
.ensure_gpu_cache_primed()
.context("ensure_gpu_cache_primed before drafter upload")?;
let drafter_tensors = model.with_gpu_cache_mut(|device, _reg| {
DFlashModelTensors::upload(device, &drafter_cfg, &drafter_weights)
.context("upload Qwen35 DFlash drafter weights to GPU")
})?;
let max_pos = model.cfg.max_position_embeddings as usize;
let bounded_max_new = max_new_tokens.min(max_pos);
let drafter_cache_cap_usize = prompt_tokens
.len()
.checked_add(bounded_max_new)
.and_then(|s| s.checked_add(32))
.ok_or_else(|| anyhow::anyhow!("drafter_cache_cap overflow"))?
.max(2048);
anyhow::ensure!(
drafter_cache_cap_usize <= u32::MAX as usize,
"drafter_cache_cap {} > u32::MAX",
drafter_cache_cap_usize,
);
let drafter_cache_cap = drafter_cache_cap_usize as u32;
let mut drafter_cache = model.with_gpu_cache_mut(|device, _reg| {
DFlashKvCache::new(device, &drafter_cfg, drafter_cache_cap)
.context("allocate Qwen35 DFlash drafter KV cache")
})?;
eprintln!(
"[HF2Q_SPEC_DFLASH qwen35] drafter loaded in {:.2}s (cfg={}, block_size={block_size}, \
cache_cap={drafter_cache_cap}, target_layers={:?})",
t_load.elapsed().as_secs_f64(),
cfg_path.display(),
drafter_cfg.target_layer_ids,
);
let kv_max_seq_usize = prompt_tokens
.len()
.checked_add(bounded_max_new)
.and_then(|s| s.checked_add(block_size as usize))
.ok_or_else(|| anyhow::anyhow!("kv_max_seq overflow"))?;
anyhow::ensure!(
kv_max_seq_usize <= u32::MAX as usize,
"kv_max_seq {} > u32::MAX",
kv_max_seq_usize,
);
anyhow::ensure!(
kv_max_seq_usize <= max_pos + block_size as usize,
"kv_max_seq {} exceeds model max_position_embeddings+block_size = {}",
kv_max_seq_usize,
max_pos + block_size as usize,
);
let kv_max_seq = kv_max_seq_usize as u32;
let mut kv_cache = model.with_gpu_cache_mut(|device, _reg| {
crate::inference::models::qwen35::kv_cache::HybridKvCache::new(
&model.cfg, device, kv_max_seq, 1,
)
.context("alloc Qwen35 DFlash HybridKvCache")
})?;
let mut gpu = crate::serve::gpu::GpuContext::new()
.map_err(|e| anyhow::anyhow!("GpuContext::new for trait API: {e}"))?;
let t_gen = std::time::Instant::now();
let mut target = Qwen35DFlashTarget::new(model, &mut kv_cache);
let output_tokens = dispatch_qwen35_dflash_generate(
&mut target,
&drafter_tensors,
&mut drafter_cache,
&drafter_cfg,
prompt_tokens,
max_new_tokens,
block_size,
effective_eos,
&mut gpu,
)
.context("dispatch_qwen35_dflash_generate")?;
let gen_elapsed = t_gen.elapsed();
let new_tokens = &output_tokens[prompt_tokens.len()..];
let decoded = tokenizer
.decode(new_tokens, false)
.unwrap_or_else(|e| format!("<decode failed: {e}>"));
println!("{decoded}");
eprintln!(
"[HF2Q_SPEC_DFLASH qwen35] {} new tokens in {:.2}s ({:.1} tok/s)",
new_tokens.len(),
gen_elapsed.as_secs_f64(),
new_tokens.len() as f64 / gen_elapsed.as_secs_f64().max(1e-6),
);
Ok(Some(()))
}
pub fn try_dispatch_qwen35_eagle3_spec_decode(
model: &mut crate::inference::models::qwen35::model::Qwen35Model,
prompt_tokens: &[u32],
max_new_tokens: usize,
eos_token_ids: &[u32],
ignore_eos: bool,
tokenizer: &tokenizers::Tokenizer,
) -> Result<Option<()>> {
if std::env::var("HF2Q_SPEC_EAGLE3").as_deref() != Ok("1") {
return Ok(None);
}
eprintln!(
"[HF2Q_SPEC_EAGLE3=1 qwen35] research-quality EAGLE-3 tree-verify path. \
Default production paths remain HF2Q_SPEC_DECODE / HF2Q_SPEC_DFLASH."
);
let Some(drafter_dir) = resolve_qwen35_eagle3_drafter_path()? else {
eprintln!(
"HF2Q_SPEC_EAGLE3=1 but HF2Q_EAGLE3_DRAFTER_PATH is unset; falling back to standard decode."
);
return Ok(None);
};
if !drafter_dir.is_dir() {
eprintln!(
"HF2Q_SPEC_EAGLE3=1 but drafter dir {} does not exist; falling back to standard decode.",
drafter_dir.display()
);
return Ok(None);
}
let weights_path = drafter_dir.join("model.safetensors");
if !weights_path.exists() {
eprintln!(
"HF2Q_SPEC_EAGLE3=1 but drafter weights {} do not exist; falling back to standard decode.",
weights_path.display()
);
return Ok(None);
}
use crate::inference::spec_decode::eagle3::tensors::Eagle3DrafterTensors;
use crate::inference::spec_decode::eagle3::weights::Eagle3Weights;
use crate::inference::spec_decode::eagle3_orchestrator::{
default_qwen35_eagle3_drafter_config, Eagle3Orchestrator, Eagle3OrchestratorConfig,
};
let drafter_cfg = default_qwen35_eagle3_drafter_config(model);
let weights_bytes = std::fs::read(&weights_path)
.with_context(|| format!("read EAGLE-3 drafter weights {}", weights_path.display()))?;
let weights = Eagle3Weights::load(&weights_bytes, &drafter_cfg)
.map_err(|e| anyhow::anyhow!("load EAGLE-3 drafter weights: {e}"))?;
model
.ensure_gpu_cache_primed()
.context("ensure_gpu_cache_primed before EAGLE-3 drafter upload")?;
let drafter_tensors = model.with_gpu_cache_mut(|device, _| {
Eagle3DrafterTensors::upload(device, &drafter_cfg, &weights)
.map_err(|e| anyhow::anyhow!("upload EAGLE-3 drafter tensors: {e}"))
})?;
let cfg = Eagle3OrchestratorConfig::qwen35_default(
model,
max_new_tokens.max(1),
eos_token_ids,
ignore_eos,
);
let max_seq = prompt_tokens
.len()
.checked_add(max_new_tokens)
.and_then(|v| v.checked_add(cfg.dynamic_tree.budget))
.ok_or_else(|| anyhow::anyhow!("EAGLE-3 max_seq overflow"))?
.max(128)
.min(model.cfg.max_position_embeddings as usize);
let mut orchestrator =
Eagle3Orchestrator::new(model, cfg, &drafter_cfg, &drafter_tensors, max_seq)?;
let started = std::time::Instant::now();
let tokens = orchestrator.generate(model, prompt_tokens, Some(tokenizer))?;
eprintln!(
"[HF2Q_SPEC_EAGLE3] {} new tokens in {:.2}s ({:.1} tok/s)",
tokens.len(),
started.elapsed().as_secs_f64(),
tokens.len() as f64 / started.elapsed().as_secs_f64().max(1e-6),
);
Ok(Some(()))
}
fn resolve_qwen35_eagle3_drafter_path() -> Result<Option<std::path::PathBuf>> {
match std::env::var("HF2Q_EAGLE3_DRAFTER_PATH") {
Ok(s) if !s.trim().is_empty() => Ok(Some(std::path::PathBuf::from(s))),
Ok(_) | Err(std::env::VarError::NotPresent) => Ok(None),
Err(e) => Err(anyhow::anyhow!("read HF2Q_EAGLE3_DRAFTER_PATH: {e}")),
}
}
pub fn try_dispatch_gemma4_eagle3_spec_decode(
target: &mut crate::inference::models::gemma4::MlxModelWeights,
prompt_tokens: &[u32],
max_new_tokens: usize,
eos_token_ids: &[u32],
ignore_eos: bool,
tokenizer: &tokenizers::Tokenizer,
gpu: &mut crate::serve::gpu::GpuContext,
) -> Result<Option<()>> {
if std::env::var("HF2Q_SPEC_EAGLE3").as_deref() != Ok("1") {
return Ok(None);
}
eprintln!(
"[HF2Q_SPEC_EAGLE3=1 gemma4] research-quality EAGLE-3 tree-verify path. \
Default production paths remain HF2Q_SPEC_DFLASH / standard decode."
);
let Some(drafter_dir) = resolve_qwen35_eagle3_drafter_path()? else {
eprintln!(
"HF2Q_SPEC_EAGLE3=1 but HF2Q_EAGLE3_DRAFTER_PATH is unset; falling back to standard decode."
);
return Ok(None);
};
if !drafter_dir.is_dir() {
eprintln!(
"HF2Q_SPEC_EAGLE3=1 but drafter dir {} does not exist; falling back to standard decode.",
drafter_dir.display()
);
return Ok(None);
}
let weights_path = drafter_dir.join("model.safetensors");
if !weights_path.exists() {
eprintln!(
"HF2Q_SPEC_EAGLE3=1 but drafter weights {} do not exist; falling back to standard decode.",
weights_path.display()
);
return Ok(None);
}
use crate::inference::spec_decode::eagle3::tensors::Eagle3DrafterTensors;
use crate::inference::spec_decode::eagle3::weights::Eagle3Weights;
use crate::inference::spec_decode::eagle3_orchestrator::{
default_gemma4_eagle3_drafter_config, default_gemma4_eagle3_orchestrator_config,
Gemma4Eagle3Orchestrator,
};
let t_load = std::time::Instant::now();
let drafter_cfg = default_gemma4_eagle3_drafter_config(target.vocab_size);
let weights_bytes = std::fs::read(&weights_path)
.with_context(|| format!("read EAGLE-3 drafter weights {}", weights_path.display()))?;
let weights = Eagle3Weights::load(&weights_bytes, &drafter_cfg)
.map_err(|e| anyhow::anyhow!("load EAGLE-3 drafter weights: {e}"))?;
let drafter_tensors = {
let (exec, _reg) = gpu.split();
Eagle3DrafterTensors::upload(exec.device(), &drafter_cfg, &weights)
.map_err(|e| anyhow::anyhow!("upload EAGLE-3 drafter tensors: {e}"))?
};
eprintln!(
"[HF2Q_SPEC_EAGLE3 gemma4] drafter loaded in {:.2}s ({} → {} GPU bytes)",
t_load.elapsed().as_secs_f64(),
weights_path.display(),
drafter_tensors.gpu_resident_bytes(),
);
let cfg = default_gemma4_eagle3_orchestrator_config(
target.layers.len(),
target.hidden_size,
target.vocab_size,
max_new_tokens.max(1),
eos_token_ids,
ignore_eos,
);
let kv_capacity = prompt_tokens
.len()
.checked_add(max_new_tokens)
.and_then(|v| v.checked_add(cfg.dynamic_tree.budget))
.and_then(|v| v.checked_add(32))
.ok_or_else(|| anyhow::anyhow!("EAGLE-3 kv_capacity overflow"))?
.max(512);
let mut orchestrator =
Gemma4Eagle3Orchestrator::new(cfg, &drafter_cfg, &drafter_tensors, kv_capacity)?;
let started = std::time::Instant::now();
let tokens = orchestrator
.generate(target, gpu, prompt_tokens, Some(tokenizer))
.context("Gemma4Eagle3Orchestrator::generate")?;
let elapsed = started.elapsed();
println!();
eprintln!(
"[HF2Q_SPEC_EAGLE3 gemma4] {} new tokens in {:.2}s ({:.1} tok/s)",
tokens.len(),
elapsed.as_secs_f64(),
tokens.len() as f64 / elapsed.as_secs_f64().max(1e-6),
);
Ok(Some(()))
}
fn resolve_ngram_k() -> Result<u32> {
match std::env::var("HF2Q_SPEC_NGRAM_K") {
Err(_) => Ok(3),
Ok(s) => {
let n: u32 = s
.parse()
.with_context(|| format!("HF2Q_SPEC_NGRAM_K must be integer; got {s:?}"))?;
anyhow::ensure!(n >= 1, "HF2Q_SPEC_NGRAM_K must be ≥ 1; got {n}");
Ok(n)
}
}
}
fn resolve_ngram_min() -> Result<u32> {
match std::env::var("HF2Q_SPEC_NGRAM_MIN") {
Err(_) => Ok(1),
Ok(s) => Ok(s
.parse()
.with_context(|| format!("HF2Q_SPEC_NGRAM_MIN must be integer; got {s:?}"))?),
}
}
fn resolve_ngram_max() -> Result<u32> {
match std::env::var("HF2Q_SPEC_NGRAM_MAX") {
Err(_) => Ok(3),
Ok(s) => Ok(s
.parse()
.with_context(|| format!("HF2Q_SPEC_NGRAM_MAX must be integer; got {s:?}"))?),
}
}
pub fn try_dispatch_ngram_spec_decode(
target: &mut crate::inference::models::gemma4::MlxModelWeights,
prompt_tokens: &[u32],
max_new_tokens: usize,
eos_token_ids: &[u32],
ignore_eos: bool,
tokenizer: &tokenizers::Tokenizer,
gpu: &mut crate::serve::gpu::GpuContext,
) -> Result<Option<()>> {
if std::env::var("HF2Q_SPEC_NGRAM").as_deref() != Ok("1") {
return Ok(None);
}
let k = resolve_ngram_k()?;
let min_ngram = resolve_ngram_min()?;
let max_ngram = resolve_ngram_max()?;
eprintln!(
"[HF2Q_SPEC_NGRAM=1] enabled — pure-CPU ngram proposer K={k} \
min_ngram={min_ngram} max_ngram={max_ngram} \
(workload-specific; ~80% accept needed to beat baseline)"
);
let effective_eos: &[u32] = if ignore_eos { &[] } else { eos_token_ids };
let t_gen = std::time::Instant::now();
let output_tokens = crate::inference::spec_decode::ngram_orchestrator::dispatch_ngram_generate(
target,
prompt_tokens,
max_new_tokens,
k,
min_ngram,
max_ngram,
effective_eos,
gpu,
)
.context("dispatch_ngram_generate")?;
let gen_elapsed = t_gen.elapsed();
let new_tokens = &output_tokens[prompt_tokens.len()..];
let decoded = tokenizer
.decode(new_tokens, false)
.unwrap_or_else(|e| format!("<decode failed: {e}>"));
println!("{decoded}");
eprintln!(
"[HF2Q_SPEC_NGRAM] {} new tokens in {:.2}s ({:.1} tok/s)",
new_tokens.len(),
gen_elapsed.as_secs_f64(),
new_tokens.len() as f64 / gen_elapsed.as_secs_f64().max(1e-6),
);
Ok(Some(()))
}