|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| use std::sync::Arc;
|
| use std::time::SystemTime;
|
|
|
| use ndarray::{s, Array2};
|
| use ort::Session;
|
| use tokio::sync::mpsc;
|
| use tokio::time::{interval, Duration};
|
|
|
| use crate::types::{
|
| DaemonConfig, EntailmentAttestation, Verdict, VerifyRequest, VerifyResponse,
|
| };
|
|
|
|
|
|
|
| fn softmax_entailment(logits: &[f32]) -> f32 {
|
|
|
| let max_l = logits.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
|
| let exps: Vec<f32> = logits.iter().map(|l| (l - max_l).exp()).collect();
|
| let sum: f32 = exps.iter().sum();
|
| exps[2] / sum
|
| }
|
|
|
|
|
|
|
| async fn execute_batch(
|
| batch: &mut Vec<VerifyRequest>,
|
| session: &Arc<Session>,
|
| cfg: &DaemonConfig,
|
| ledger_tx: &mpsc::Sender<([u8; 32], Vec<u8>)>,
|
| ) {
|
| if batch.is_empty() {
|
| return;
|
| }
|
|
|
| let batch_size = batch.len();
|
|
|
|
|
|
|
| let max_len = batch
|
| .iter()
|
| .map(|r| r.input_ids.len())
|
| .max()
|
| .unwrap_or(0);
|
|
|
| let mut input_ids_arr = Array2::<i64>::zeros((batch_size, max_len));
|
| let mut attention_mask_arr = Array2::<i64>::zeros((batch_size, max_len));
|
| let mut token_types_arr = Array2::<i64>::zeros((batch_size, max_len));
|
|
|
| for (i, req) in batch.iter().enumerate() {
|
| let len = req.input_ids.len().min(max_len);
|
| input_ids_arr
|
| .slice_mut(s![i, ..len])
|
| .assign(&ndarray::ArrayView::from(&req.input_ids[..len]));
|
| attention_mask_arr
|
| .slice_mut(s![i, ..len])
|
| .assign(&ndarray::ArrayView::from(&req.attention_mask[..len]));
|
| token_types_arr
|
| .slice_mut(s![i, ..len])
|
| .assign(&ndarray::ArrayView::from(&req.token_type_ids[..len]));
|
| }
|
|
|
|
|
| let session_clone = Arc::clone(session);
|
| let outputs = tokio::task::spawn_blocking(move || {
|
| let inputs = ort::inputs![
|
| "input_ids" => input_ids_arr,
|
| "attention_mask" => attention_mask_arr,
|
| "token_type_ids" => token_types_arr,
|
| ]
|
| .expect("input construction failed");
|
| session_clone.run(inputs).expect("TRT inference failed")
|
| })
|
| .await
|
| .expect("spawn_blocking panicked");
|
|
|
|
|
| let logits_tensor = outputs["logits"]
|
| .extract_tensor::<f32>()
|
| .expect("logits extraction failed");
|
| let logits_view = logits_tensor.view();
|
|
|
| let timestamp_ns = SystemTime::now()
|
| .duration_since(SystemTime::UNIX_EPOCH)
|
| .unwrap()
|
| .as_nanos() as u64;
|
|
|
|
|
| for (i, req) in batch.drain(..).enumerate() {
|
| let row: Vec<f32> = logits_view
|
| .row(i)
|
| .iter()
|
| .cloned()
|
| .collect();
|
| let score = softmax_entailment(&row);
|
| let verdict = Verdict::from_score(score, cfg.threshold);
|
|
|
|
|
| let attestation = EntailmentAttestation {
|
| timestamp_ns,
|
| chunk_id: req.chunk_id.clone(),
|
| claim_text: req.claim_text.clone(),
|
| entailment_score: score,
|
| verdict: format!("{:?}", verdict),
|
| model_signature: cfg.model_signature.clone(),
|
| threshold: cfg.threshold,
|
| };
|
| let (hash, payload) = attestation.seal();
|
|
|
|
|
| let _ = ledger_tx.try_send((hash, payload));
|
|
|
|
|
| let _ = req.responder.send(VerifyResponse {
|
| entailment_score: score,
|
| label: verdict,
|
| attestation_hash: hash,
|
| });
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| pub async fn run_inference_daemon(
|
| mut rx: mpsc::Receiver<VerifyRequest>,
|
| session: Arc<Session>,
|
| cfg: Arc<DaemonConfig>,
|
| ledger_tx: mpsc::Sender<([u8; 32], Vec<u8>)>,
|
| ) {
|
| let mut batch: Vec<VerifyRequest> = Vec::with_capacity(cfg.max_batch_size);
|
| let mut flush_timer = interval(Duration::from_millis(cfg.flush_interval_ms));
|
|
|
| log::info!(
|
| "[daemon] running β max_batch={} flush_interval={}ms threshold={}",
|
| cfg.max_batch_size,
|
| cfg.flush_interval_ms,
|
| cfg.threshold
|
| );
|
|
|
| loop {
|
| tokio::select! {
|
|
|
| Some(req) = rx.recv() => {
|
| batch.push(req);
|
| if batch.len() >= cfg.max_batch_size {
|
| execute_batch(&mut batch, &session, &cfg, &ledger_tx).await;
|
| }
|
| }
|
|
|
| _ = flush_timer.tick() => {
|
| if !batch.is_empty() {
|
| execute_batch(&mut batch, &session, &cfg, &ledger_tx).await;
|
| }
|
| }
|
|
|
| else => {
|
| if !batch.is_empty() {
|
| execute_batch(&mut batch, &session, &cfg, &ledger_tx).await;
|
| }
|
| log::info!("[daemon] channel closed, shutting down");
|
| break;
|
| }
|
| }
|
| }
|
| }
|
|
|