use std::env;
use std::fs;
use std::path::PathBuf;
use poly_kv::policy::CompressionPolicy;
use poly_kv::shape::{AttentionType, KvTensorShape};
use poly_kv::SharedKVPool;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
struct InputJson {
shape: ShapeJson,
tokens: Vec<TokenJson>,
seed: Option<u64>,
}
#[derive(Debug, Deserialize)]
struct ShapeJson {
attention_type: String,
num_layers: u32,
num_heads: u32,
num_kv_heads: u32,
head_dim: usize,
hidden_size: usize,
}
#[derive(Debug, Deserialize)]
struct TokenJson {
id: String,
vector: Vec<f32>,
}
#[derive(Debug, Serialize)]
struct OutputManifest {
pool_id: String,
num_shared_tokens: u32,
num_layers: u32,
num_kv_heads: u32,
head_dim: usize,
shared_codec: String,
compression_ratio: f64,
pool_size_bytes: u64,
total_compressed_bytes: u64,
backend: String,
fib_build_ms: u64,
build_seed: u64,
built_at_unix: i64,
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
eprintln!("usage: {} <input.json> <output_dir>", args[0]);
std::process::exit(1);
}
let input_path = PathBuf::from(&args[1]);
let output_dir = PathBuf::from(&args[2]);
fs::create_dir_all(&output_dir).expect("create output dir");
let input_bytes = fs::read(&input_path).expect("read input");
let input: InputJson = serde_json::from_slice(&input_bytes).expect("parse input json");
let attn_type = match input.shape.attention_type.as_str() {
"MHA" => AttentionType::MHA,
"GQA" => AttentionType::GQA,
"MQA" => AttentionType::MQA,
other => panic!("unknown attention_type: {other}"),
};
let shape = KvTensorShape {
attention_type: attn_type,
num_layers: input.shape.num_layers,
num_heads: input.shape.num_heads,
num_kv_heads: input.shape.num_kv_heads,
head_dim: input.shape.head_dim,
hidden_size: input.shape.hidden_size,
};
let corpus: Vec<(String, Vec<f32>)> =
input.tokens.into_iter().map(|t| (t.id, t.vector)).collect();
let seed = input.seed.unwrap_or(42);
let _policy = CompressionPolicy::default_two_tier();
eprintln!(
"[poly-kv] building pool: shape={:?} num_tokens={} num_layers={} num_kv_heads={} head_dim={}",
attn_type,
corpus.len(),
shape.num_layers,
shape.num_kv_heads,
shape.head_dim
);
let (pool, receipt) = SharedKVPool::build(&corpus, &shape, seed).expect("build pool");
eprintln!(
"[poly-kv] build ok: pool_id={} backend={} codec={:?} compression_ratio={:.2}x size={} bytes",
&pool.manifest.pool_id.hex()[..12],
receipt.backend,
pool.manifest.shared_codec,
pool.manifest.compression_ratio,
pool.manifest.pool_size_bytes
);
let output_manifest = OutputManifest {
pool_id: pool.manifest.pool_id.hex().to_string(),
num_shared_tokens: pool.manifest.num_shared_tokens,
num_layers: pool.manifest.num_layers,
num_kv_heads: pool.manifest.shape.num_kv_heads,
head_dim: pool.manifest.shape.head_dim,
shared_codec: format!("{:?}", pool.manifest.shared_codec),
compression_ratio: pool.manifest.compression_ratio,
pool_size_bytes: pool.manifest.pool_size_bytes,
total_compressed_bytes: receipt.pool_size_bytes,
backend: receipt.backend.clone(),
fib_build_ms: receipt.fib_build_ms,
build_seed: pool.manifest.build_seed,
built_at_unix: pool.manifest.built_at_unix,
};
fs::write(
output_dir.join("manifest.json"),
serde_json::to_vec_pretty(&output_manifest).expect("serialize manifest"),
)
.expect("write manifest");
let receipt_json = serde_json::to_string_pretty(&receipt).expect("serialize receipt");
fs::write(output_dir.join("receipt.json"), receipt_json).expect("write receipt");
for layer_idx in 0..pool.manifest.num_layers as usize {
let decompressed = pool.decompress_layer(layer_idx).expect("decompress layer");
let json = serde_json::to_vec(&decompressed).expect("serialize layer");
fs::write(output_dir.join(format!("layer_{layer_idx:03}.json")), json)
.expect("write layer");
}
eprintln!(
"[poly-kv] wrote {} layer files to {}",
pool.manifest.num_layers,
output_dir.display()
);
}