Skip to content

Commit 37ebe99

Browse files
committed
WIP: Pipeline Creation and Validation
All steps (download, conversion, loading, inference) are Success && reported via your Reporter. The test completes successfully, prints the top token/logit, and generates a report. No trait/orphan/circular dependency issues remain. next : speed, gpu, cpu test for actual modal that is converted , streamming vs non streamming loading time and bunch of correctness on actual modal left
1 parent 9950b8a commit 37ebe99

27 files changed

Lines changed: 5683 additions & 3903 deletions

crates/bitnet-converter/src/lib.rs

Lines changed: 62 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,63 @@
1-
use std::path::PathBuf;
2-
use serde::Deserialize;
3-
use bincode::serde::encode_to_vec;
4-
use bincode::config::standard;
5-
use bitnet_tools::constants::{workspace_root, CONFIG_JSON, SAFETENSORS_FILE};
6-
use std::time::Instant;
7-
use rayon::prelude::*;
8-
9-
pub mod packer;
10-
pub mod source;
11-
12-
#[derive(Deserialize)]
13-
struct PartialConfig {
14-
num_hidden_layers: usize,
15-
}
16-
17-
/// Programmatic API to run the BitNet conversion pipeline.
18-
/// Returns the path to the output packed model file on success.
19-
pub fn convert_model_on_disk(input_dir: &str, output_dir: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
20-
let start = Instant::now();
21-
let input_path = workspace_root().join(input_dir);
22-
let output_path = workspace_root().join(output_dir);
23-
std::fs::create_dir_all(&output_path)?;
24-
println!("[CONVERT] Loading config from: {}", input_path.join(CONFIG_JSON).display());
25-
let t0 = Instant::now();
26-
let config_str = std::fs::read_to_string(input_path.join(CONFIG_JSON))?;
27-
let config: PartialConfig = serde_json::from_str(&config_str)?;
28-
println!("[CONVERT] Loaded config in {:.2?}", t0.elapsed());
29-
println!("[CONVERT] Loading safetensors from: {}", input_path.join(SAFETENSORS_FILE).display());
30-
let t1 = Instant::now();
31-
let source = source::ModelSource::SafetensorsFile(
32-
input_path.join(SAFETENSORS_FILE).to_str().unwrap().to_string(),
33-
);
34-
let tensor_map = source.load_tensors()?;
35-
println!("[CONVERT] Loaded safetensors in {:.2?}", t1.elapsed());
36-
println!("[CONVERT] Packing weights...");
37-
let t2 = Instant::now();
38-
let record = packer::convert_model(tensor_map, config.num_hidden_layers, true)?;
39-
println!("[CONVERT] Packed weights in {:.2?}", t2.elapsed());
40-
println!("[CONVERT] Writing model as per-block files to: {}", output_path.display());
41-
let t3 = Instant::now();
42-
// Save embedding
43-
let embedding_path = output_path.join("embedding.bin");
44-
let embedding_bytes = encode_to_vec(&record.embedding, standard())?;
45-
std::fs::write(&embedding_path, &embedding_bytes)?;
46-
// Save norm
47-
let norm_path = output_path.join("norm.bin");
48-
let norm_bytes = encode_to_vec(&record.norm, standard())?;
49-
std::fs::write(&norm_path, &norm_bytes)?;
50-
// Save lm_head
51-
let lm_head_path = output_path.join("lm_head.bin");
52-
let lm_head_bytes = encode_to_vec(&record.lm_head, standard())?;
53-
std::fs::write(&lm_head_path, &lm_head_bytes)?;
54-
// Save each block in parallel
55-
record.blocks.par_iter().enumerate().for_each(|(i, block)| {
56-
let block_path = output_path.join(format!("block_{}.bin", i));
57-
let block_bytes = encode_to_vec(block, standard()).expect("Failed to encode block");
58-
std::fs::write(&block_path, &block_bytes).expect("Failed to write block file");
59-
});
60-
println!("[CONVERT] Wrote all model parts in {:.2?}", t3.elapsed());
61-
println!("[CONVERT] Total conversion time: {:.2?}", start.elapsed());
62-
Ok(output_path)
1+
use std::path::PathBuf;
2+
use serde::Deserialize;
3+
use bincode::serde::encode_to_vec;
4+
use bincode::config::standard;
5+
use bitnet_tools::constants::{workspace_root, CONFIG_JSON, SAFETENSORS_FILE};
6+
use std::time::Instant;
7+
use rayon::prelude::*;
8+
9+
pub mod packer;
10+
pub mod source;
11+
12+
#[derive(Deserialize)]
13+
struct PartialConfig {
14+
num_hidden_layers: usize,
15+
}
16+
17+
/// Programmatic API to run the BitNet conversion pipeline.
18+
/// Returns the path to the output packed model file on success.
19+
pub fn convert_model_on_disk(input_dir: &str, output_dir: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
20+
let start = Instant::now();
21+
let input_path = workspace_root().join(input_dir);
22+
let output_path = workspace_root().join(output_dir);
23+
std::fs::create_dir_all(&output_path)?;
24+
println!("[CONVERT] Loading config from: {}", input_path.join(CONFIG_JSON).display());
25+
let t0 = Instant::now();
26+
let config_str = std::fs::read_to_string(input_path.join(CONFIG_JSON))?;
27+
let config: PartialConfig = serde_json::from_str(&config_str)?;
28+
println!("[CONVERT] Loaded config in {:.2?}", t0.elapsed());
29+
println!("[CONVERT] Loading safetensors from: {}", input_path.join(SAFETENSORS_FILE).display());
30+
let t1 = Instant::now();
31+
let source = source::ModelSource::SafetensorsFile(
32+
input_path.join(SAFETENSORS_FILE).to_str().unwrap().to_string(),
33+
);
34+
let tensor_map = source.load_tensors()?;
35+
println!("[CONVERT] Loaded safetensors in {:.2?}", t1.elapsed());
36+
println!("[CONVERT] Packing weights...");
37+
let t2 = Instant::now();
38+
let record = packer::convert_model(tensor_map, config.num_hidden_layers, true)?;
39+
println!("[CONVERT] Packed weights in {:.2?}", t2.elapsed());
40+
println!("[CONVERT] Writing model as per-block files to: {}", output_path.display());
41+
let t3 = Instant::now();
42+
// Save embedding
43+
let embedding_path = output_path.join("embedding.bin");
44+
let embedding_bytes = encode_to_vec(&record.embedding, standard())?;
45+
std::fs::write(&embedding_path, &embedding_bytes)?;
46+
// Save norm
47+
let norm_path = output_path.join("norm.bin");
48+
let norm_bytes = encode_to_vec(&record.norm, standard())?;
49+
std::fs::write(&norm_path, &norm_bytes)?;
50+
// Save lm_head
51+
let lm_head_path = output_path.join("lm_head.bin");
52+
let lm_head_bytes = encode_to_vec(&record.lm_head, standard())?;
53+
std::fs::write(&lm_head_path, &lm_head_bytes)?;
54+
// Save each block in parallel
55+
record.blocks.par_iter().enumerate().for_each(|(i, block)| {
56+
let block_path = output_path.join(format!("block_{}.bin", i));
57+
let block_bytes = encode_to_vec(block, standard()).expect("Failed to encode block");
58+
std::fs::write(&block_path, &block_bytes).expect("Failed to write block file");
59+
});
60+
println!("[CONVERT] Wrote all model parts in {:.2?}", t3.elapsed());
61+
println!("[CONVERT] Total conversion time: {:.2?}", start.elapsed());
62+
Ok(output_path)
6363
}

crates/bitnet-converter/src/packer.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use serde::{Serialize, Deserialize};
55
use rayon::prelude::*;
66
use thiserror::Error;
77
use std::time::Instant;
8-
use rand::{self};
8+
99

1010
// ================================================================================================
1111
// Error Handling
@@ -224,6 +224,9 @@ pub fn quantize_to_1_58_bit_optimized(tensor: &[f32], shape: &[usize]) -> (Vec<i
224224

225225
pub fn pack_ternary_weights_optimized(weights: &[i8]) -> Result<Vec<u32>> {
226226
if weights.len() % 16 != 0 {
227+
// To make this more robust for models where K is not a multiple of 16,
228+
// we can pad the input weights. For now, we keep the strict check.
229+
// A more robust implementation would pad `weights` with 0s to a multiple of 16 here.
227230
return Err(ConversionError::InvalidWeightCount { count: weights.len() });
228231
}
229232

@@ -234,12 +237,15 @@ pub fn pack_ternary_weights_optimized(weights: &[i8]) -> Result<Vec<u32>> {
234237

235238
// Unroll loop for better performance
236239
for i in 0..16 {
240+
// CORRECTED: This mapping now matches the WGSL kernel's decoding logic.
241+
// WGSL decode: 1 -> +1, 2 -> -1, 0/3 -> 0
237242
let encoded = match chunk[i] {
238-
-1 => 0u32,
239-
0 => 1u32,
240-
1 => 2u32,
243+
1 => 1u32, // 01
244+
-1 => 2u32, // 10
245+
0 => 0u32, // 00
241246
invalid => return Err(ConversionError::InvalidTernaryWeight { value: invalid }),
242247
};
248+
// Pack with LSB-first ordering to match the kernel
243249
packed_val |= encoded << (i * 2);
244250
}
245251

@@ -536,9 +542,9 @@ mod tests {
536542
for i in 0..16 {
537543
let two_bits = ((packed_u32 >> (i * 2)) & 3) as u8;
538544
let val_i8 = match two_bits {
539-
0 => -1,
540-
1 => 0,
541-
2 => 1,
545+
0 => 0, // 00 -> 0
546+
1 => 1, // 01 -> 1
547+
2 => -1, // 10 -> -1
542548
_ => 0,
543549
};
544550
i8_data.push(val_i8);

0 commit comments

Comments
 (0)