A high-quality, tiny BitNet b1.58-style Mixture-of-Experts Vision-Language Model (~300M ternary parameters) designed for edge deployment.
What it does: You give it an image + a question, it gives you an intelligent answer. OCR, chart analysis, document understanding, visual reasoning - all in <500MB.
Follow these steps in order to set up and train EmberNet:
# Navigate to project directory
cd EmberNet
# Install all required packages
pip install -r requirements.txt
Required packages:
torch>=2.0.0 - Deep learning frameworktransformers>=4.36.0 - HuggingFace transformers (for SigLIP)datasets>=2.14.0 - HuggingFace datasetswandb>=0.16.0 - Experiment trackinghuggingface_hub>=0.19.0 - HuggingFace authenticationPillow>=9.0.0 - Image processingeinops>=0.7.0 - Tensor operationsnumpy>=1.24.0 - Numerical computing2a. HuggingFace Login (REQUIRED - for downloading datasets):
# Login to HuggingFace
huggingface-cli login
# When prompted, enter your HuggingFace token
# Get your token from: https://huggingface.co/settings/tokens
# Create a token with "read" permissions
2b. Weights & Biases Login (OPTIONAL - for experiment tracking):
# Login to W&B
wandb login
# When prompted, enter your W&B API key
# Get your API key from: https://wandb.ai/authorize
# This enables training visualization and metric tracking
Choose one option based on your needs:
Option A: All Datasets (~100GB) - Best quality, all datasets
python training/prepare_data.py --all --output-dir ./data
Downloads all 20 datasets for maximum model quality.
Option B: Recommended (~70GB) - Good balance of quality and size
python training/prepare_data.py --recommended --output-dir ./data
Downloads critical + recommended datasets (excludes optional ones).
Option C: Critical Only (~50GB) - Core datasets only
python training/prepare_data.py --critical --output-dir ./data
Downloads only the most important datasets.
Option D: Minimal (~10GB) - Quick testing
python training/prepare_data.py --minimal --output-dir ./data
Downloads a tiny subset for testing the pipeline.
Other useful commands:
# List all available datasets before downloading
python training/prepare_data.py --list
# Download specific datasets only
python training/prepare_data.py --dataset textvqa chartqa vqav2 --output-dir ./data
# Explain how alignment works
python training/prepare_data.py --explain
What gets downloaded:
Note: Some datasets (ShareGPT4V, ALLaVA, ChartQA, GQA, VSR) download images from URLs, which may take longer than loading pre-packaged datasets.
What gets saved: After downloading, you'll have:
./data/{dataset_name}/ - Each dataset in its own folder./data/download_manifest.json - Central tracking file with all download info./data/dataset_index.json - Index mapping datasets to stages/domains./data/{dataset_name}/metadata.json - Individual dataset metadataThe download_manifest.json tracks:
EmberNet uses a two-stage training pipeline. Use --trial for quick validation or --main for full training.
π Quick Start: Single-Command Training
# Trial Run - Quick pipeline validation (minutes, not hours)
python training/train.py --trial --data-dir ./data
# Main Run - Full production training (hours/days)
python training/train.py --main --data-dir ./data
Both commands automatically run Stage 1 β Stage 2 sequentially.
Trial Mode (--trial)
Validates the entire pipeline with minimal data:
python training/train.py --trial --data-dir ./data
| Setting | Value |
|---|---|
| Samples per dataset | 50 |
| Epochs per stage | 1 |
| Batch size | 2 |
| Gradient accumulation | 1 |
| W&B logging | Disabled |
| Output | ./checkpoints/trial/stage{1,2}/ |
Use this to verify everything works before committing to full training.
Main Mode (--main)
Full production training with all data:
python training/train.py --main --data-dir ./data
| Setting | Stage 1 | Stage 2 |
|---|---|---|
| Samples | ALL | ALL |
| Epochs | 3 | 10 |
| Batch size | 8 | 4 |
| Gradient accumulation | 4 | 4 |
| W&B logging | Enabled | Enabled |
| Output | ./checkpoints/stage1/ | ./checkpoints/stage2/ |
Run a Specific Stage Only
# Stage 1 only (projector alignment)
python training/train.py --trial --stage 1 --data-dir ./data
# Stage 2 only (expert specialization) - requires Stage 1 checkpoint
python training/train.py --trial --stage 2 --data-dir ./data \
--resume ./checkpoints/trial/stage1/final_model.pt
Training Options
# Custom epochs and batch size
python training/train.py --main --data-dir ./data --epochs 5 --batch-size 4
# Limit samples per dataset (useful for debugging)
python training/train.py --main --data-dir ./data --max-samples-per-dataset 1000
# Disable specific features
python training/train.py --main --data-dir ./data \
--no-wandb \ # Disable W&B logging
--no-ema \ # Disable EMA
--no-curriculum \ # Disable curriculum learning
--no-adaptive-clip # Disable adaptive gradient clipping
# Hardware adjustments
python training/train.py --main --data-dir ./data \
--device cpu \ # Force CPU (slower)
--no-amp \ # Disable mixed precision
--num-workers 2 # Reduce data loading workers
Output Structure
After training completes:
checkpoints/
βββ trial/ # Trial mode outputs
β βββ stage1/
β β βββ final_model.pt # Stage 1 checkpoint
β β βββ checkpoint_epoch_1.pt
β βββ stage2/
β βββ final_model.pt # Stage 2 checkpoint (final model)
β βββ checkpoint_epoch_1.pt
β
βββ stage1/ # Main mode outputs
β βββ final_model.pt
β βββ best_model.pt
β βββ checkpoint_epoch_*.pt
β
βββ stage2/
βββ final_model.pt # β Use this for inference
βββ best_model.pt
βββ checkpoint_epoch_*.pt
What Each Stage Does
| Stage | Purpose | What Trains | What's Frozen |
|---|---|---|---|
| Stage 1 | Vision-Language Alignment | CrossModal Projector, Pooler, Compressor | Vision Encoder, LM Decoder |
| Stage 2 | Expert Specialization | MoE Router, Domain Experts | Vision Encoder, Projector, Embeddings |
Convert to optimized ternary format for deployment:
python inference/convert.py \
./checkpoints/stage2/final_model.pt \
./embernet_optimized.pt
This packs ternary weights to 2-bit representation, reducing model size to <500MB.
Interactive Mode:
python inference/infer.py \
--model ./checkpoints/stage2/final_model.pt \
--interactive
Interactive commands:
/load image.jpg - Load an image/describe - Describe the image/ocr - Extract text from image/chart - Analyze chart/graph/clear - Reset conversation/quit - ExitSingle Query:
python inference/infer.py \
--model ./checkpoints/stage2/final_model.pt \
--image photo.jpg \
--prompt "What's in this image?"
# 1. Install dependencies
cd EmberNet
pip install -r requirements.txt
# 2. Authenticate (HF required, W&B optional)
huggingface-cli login # Enter token from https://huggingface.co/settings/tokens
wandb login # Optional: Enter API key from https://wandb.ai/authorize
# 3. Download training data
python training/prepare_data.py --recommended --output-dir ./data # ~70GB
# OR: python training/prepare_data.py --all --output-dir ./data # ~100GB (best quality)
# OR: python training/prepare_data.py --minimal --output-dir ./data # ~10GB (testing only)
# 4. Train the model (SINGLE COMMAND - runs Stage 1 β Stage 2 automatically)
python training/train.py --trial --data-dir ./data # Quick test (~minutes)
# OR:
python training/train.py --main --data-dir ./data # Full training (~hours/days)
# 5. Run interactive inference
python inference/infer.py --model ./checkpoints/stage2/final_model.pt --interactive
EmberNet connects images to language through a carefully designed pipeline:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β IMAGE INPUT β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 1. VISION ENCODER (SigLIP - Frozen) β β
β β β’ Pretrained on 400M image-text pairs from Google β β
β β β’ Extracts 196 visual tokens (14Γ14 grid) β β
β β β’ Each token is a 768-dimensional feature vector β β
β β β’ Already "understands" visual concepts β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 2. TOKEN COMPRESSION (Trainable) β β
β β β’ Pixel Shuffle: 196 β 49 tokens (merges 2Γ2 neighbors) β β
β β β’ Adaptive Pooling: 49 β 64 tokens (learned queries) β β
β β β’ Preserves important visual information β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 3. PROJECTOR (BitLinear MLP) β THIS IS WHERE ALIGNMENT HAPPENS β β
β β β’ 2-layer MLP with ternary weights {-1, 0, +1} β β
β β β’ Maps: Vision embedding space β Language embedding space β β
β β β’ Trained in Stage 1 on image-caption pairs β β
β β β’ After training, visual tokens "look like" word tokens β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 4. MERGED INPUT SEQUENCE β β
β β [BOS] [IMGβ] [IMGβ] ... [IMGββ] [User: Describe this] ... β β
β β βββ visual tokens βββ βββ text tokens βββββββ β β
β β The LLM processes both as if they're all "text" β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 5. BITNET MOE DECODER β β
β β β’ 16 transformer layers with ternary weights β β
β β β’ MoE FFN: 8 specialized experts + 1 shared expert β β
β β β’ Router sends tokens to relevant experts: β β
β β - OCR expert for text reading β β
β β - Chart expert for graphs β β
β β - Diagram expert for technical drawings β β
β β - etc. β β
β β β’ Generates response token by token β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β TEXT OUTPUT β
β "This image shows a bar chart comparing..." β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Stage 1 - Projector Alignment:
Stage 2 - Expert Specialization:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EMBERNET MoE EXPERT ARCHITECTURE β
β β
β ROUTING: Each token β TOP-2 Experts + SHARED Expert (always active) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β EXPERT 0: vision_ocr β
β ββ Specialty: Read text in images, OCR, document parsing β
β ββ Datasets: TextVQA, DocVQA, OCR-VQA β
β β
β EXPERT 1: vision_diagram β
β ββ Specialty: Understand diagrams, infographics, technical drawings β
β ββ Datasets: AI2D, InfoVQA β
β β
β EXPERT 2: code_math_chart β
β ββ Specialty: Analyze charts, graphs, plots, data visualizations β
β ββ Datasets: ChartQA, PlotQA, FigureQA, DVQA β
β β
β EXPERT 3: code_math_formula β
β ββ Specialty: Handle math equations, formulas, numerical reasoning β
β ββ Datasets: MathVista β
β β
β EXPERT 4: spatial_scene β
β ββ Specialty: Scene understanding, object detection, descriptions β
β ββ Datasets: VQAv2, Visual Genome β
β β
β EXPERT 5: spatial_reasoning β
β ββ Specialty: Spatial relationships, counting, positional reasoning β
β ββ Datasets: GQA β
β β
β EXPERT 6: agentic_knowledge β
β ββ Specialty: Knowledge-based QA, facts requiring world knowledge β
β ββ Datasets: OK-VQA, A-OKVQA β
β β
β EXPERT 7: agentic_reasoning β
β ββ Specialty: Multi-step reasoning, logic, science questions β
β ββ Datasets: ScienceQA, CLEVR β
β β
β SHARED EXPERT (Always Active) β
β ββ Specialty: Common patterns, language generation, general knowledge β
β ββ Datasets: ALL datasets (learns shared representations) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Example Routing:
User: "What does this chart show? The title says Q3 Sales."
β
ββ> EXPERT 0 (vision_ocr) - reads "Q3 Sales" text
ββ> EXPERT 2 (chart) - analyzes chart structure
ββ> SHARED EXPERT - general language/context
Combined output: "This bar chart shows Q3 sales data..."
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| LLaVA-Instruct-150K β | lmms-lab/LLaVA-Instruct-150K | GPT-4 generated visual conversations | 150K | ~5GB |
| ShareGPT4V β | lmms-lab/ShareGPT4V | Detailed image descriptions from GPT-4V | 100K | ~8GB |
| ALLaVA | FreedomIntelligence/ALLaVA-4V | Diverse visual instructions | 711K | ~6GB |
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| TextVQA β | lmms-lab/TextVQA | Text in natural scenes | 45K | ~2GB |
| DocVQA β | lmms-lab/DocVQA | Documents, forms, receipts | 50K | ~3GB |
| AI2D β | lmms-lab/ai2d | Scientific diagrams | 15K | ~1.5GB |
| InfoVQA | lmms-lab/InfographicVQA | Infographics | 30K | ~2.5GB |
| OCR-VQA | howard-hou/OCR-VQA | Book covers, signs | 200K | ~4GB |
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| ChartQA β | ahmed-masry/ChartQA | Bar, line, pie charts | 32K | ~1GB |
| MathVista β | AI4Math/MathVista | Mathematical visual reasoning | 6K | ~1GB |
| PlotQA | lmms-lab/PlotQA | Scientific plots | 224K | ~8GB |
| FigureQA | lmms-lab/FigureQA | Figure understanding | 180K | ~5GB |
| DVQA | lmms-lab/DVQA | Data visualization | 300K | ~3GB |
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| VQAv2 β | lmms-lab/VQAv2 | General visual QA | 1.1M | ~25GB |
| GQA | lmms-lab/GQA | Scene graph reasoning | 22M | ~15GB |
| Visual Genome | lmms-lab/VisualGenome | Dense scene annotations | 108K | ~15GB |
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| ScienceQA β | derek-thomas/ScienceQA | Science with diagrams | 21K | ~2GB |
| OK-VQA | lmms-lab/OK-VQA | Outside knowledge VQA | 14K | ~1GB |
| A-OKVQA | lmms-lab/A-OKVQA | Augmented knowledge VQA | 25K | ~1.5GB |
| CLEVR | lmms-lab/CLEVR | Compositional reasoning | 850K | ~18GB |
β = Critical (included in --critical mode)
# Minimal (~10GB) - For quick testing only
python training/prepare_data.py --minimal
# Includes: llava_instruct_150k, textvqa, chartqa, vqav2
# Critical (~45GB) - Essential for a working model
python training/prepare_data.py --critical
# Includes: All β
marked datasets above
# Recommended (~70GB) - Good quality model
python training/prepare_data.py --recommended
# Includes: Critical + recommended datasets
# All (~100GB) - Maximum quality
python training/prepare_data.py --all
# Includes: Everything
| Stage | Minimum | Recommended | Notes |
|---|---|---|---|
| Stage 1 | 8GB VRAM | 16GB VRAM | Can use CPU with 16GB RAM (slower) |
| Stage 2 | 12GB VRAM | 24GB VRAM | Can use CPU with 32GB RAM (slower) |
| Stage | GPU (A100) | GPU (RTX 3090) | CPU (32 cores) |
|---|---|---|---|
| Stage 1 (3 epochs) | 6-12 hours | 12-24 hours | 3-5 days |
| Stage 2 (10 epochs) | 24-48 hours | 48-96 hours | 10-15 days |
Basic Training (Recommended):
# Stage 1: Projector Alignment
python training/train.py \
--stage 1 \
--data-dir ./data \
--epochs 3 \
--batch-size 8 \
--output-dir ./checkpoints
# Stage 2: Expert Specialization
python training/train.py \
--stage 2 \
--data-dir ./data \
--epochs 10 \
--batch-size 4 \
--resume ./checkpoints/checkpoint_epoch_3.pt \
--output-dir ./checkpoints
With W&B Logging (Recommended):
# Stage 1
python training/train.py \
--stage 1 \
--data-dir ./data \
--epochs 3 \
--batch-size 8 \
--wandb-project EmberNet \
--wandb-run-name stage1_projector
# Stage 2
python training/train.py \
--stage 2 \
--data-dir ./data \
--epochs 10 \
--batch-size 4 \
--resume ./checkpoints/checkpoint_epoch_3.pt \
--wandb-project EmberNet \
--wandb-run-name stage2_experts
Custom Learning Rate:
python training/train.py \
--stage 1 \
--data-dir ./data \
--epochs 3 \
--lr 5e-4
Resume from Checkpoint:
python training/train.py \
--stage 2 \
--data-dir ./data \
--resume ./checkpoints/checkpoint_epoch_5.pt
Out of Memory (OOM) Errors:
# Reduce batch size and increase gradient accumulation
python training/train.py \
--stage 1 \
--data-dir ./data \
--batch-size 2 \
--grad-accum 16
# Effective batch size = 2 * 16 = 32
Slow Training:
# Check if GPU is being used
python training/train.py --stage 1 --data-dir ./data --device cuda
# Increase number of data loading workers
python training/train.py --stage 1 --data-dir ./data --num-workers 8
Mixed Precision Issues:
# Disable automatic mixed precision
python training/train.py --stage 1 --data-dir ./data --no-amp
CPU Training:
# Force CPU (much slower but works without GPU)
python training/train.py --stage 1 --data-dir ./data --device cpu --batch-size 1
Training Settings:
--stage {1,2} Training stage (1=projector, 2=expert SFT)
--epochs N Number of training epochs (default: 3)
--batch-size N Training batch size (default: 4)
--lr LR Learning rate (auto-set: 1e-3 for stage1, 1e-4 for stage2)
--grad-accum N Gradient accumulation steps (default: 4)
Paths:
--data-dir DIR Data directory (default: ./data)
--output-dir DIR Output directory for checkpoints (default: ./checkpoints)
--resume PATH Resume from checkpoint
Hardware:
--device DEVICE Device: cuda/cpu/auto (default: auto)
--no-amp Disable mixed precision training
--num-workers N Data loading workers (default: 4)
Experiment Tracking:
--wandb Use W&B logging (default: True)
--no-wandb Disable W&B logging
--wandb-project NAME W&B project name (default: EmberNet)
--wandb-run-name NAME W&B run name (default: auto-generated)
During training, the following checkpoints are saved to --output-dir:
checkpoint_epoch_N.pt - Saved after each epochcheckpoint_step_N.pt - Saved every N steps (configurable)best_model.pt - Best model based on validation lossfinal_model.pt - Final model after all epochsEach checkpoint contains:
With Weights & Biases: Visit https://wandb.ai/your-username/EmberNet to see:
Console Output:
======================================================================
Starting Stage 1 Training
======================================================================
Epochs: 3
Steps per epoch: 1000
Total steps: 3000
Batch size: 8
Gradient accumulation: 4
Effective batch size: 32
Learning rate: 0.001
Device: cuda
--- Token Statistics (per sample) ---
Total tokens: 2,048
ββ Image tokens: 64
ββ Text tokens: 1,984
--- Token Statistics (per batch) ---
Total tokens: 16,384
ββ Image tokens: 512
ββ Text tokens: 15,872
--- Total Training Tokens (all epochs) ---
Total tokens: 49,152,000
ββ Image tokens: 1,536,000
ββ Text tokens: 47,616,000
======================================================================
Epoch 1/3 | Step 100 | Loss: 2.3456 | Avg Loss: 2.4567 | LR: 1.00e-03
from inference.infer import EmberVLM
# Load model
model = EmberVLM("checkpoints/embernet.pt")
# Basic image Q&A
response = model.chat(
image="photo.jpg",
prompt="What's in this image?"
)
print(response)
# OCR - Read text from image
response = model.chat(
image="document.png",
prompt="Extract all text from this document"
)
# Chart analysis
response = model.chat(
image="chart.png",
prompt="What does this chart show? What's the maximum value?"
)
# Multi-turn conversation (remembers context)
model.chat(image="scene.jpg", prompt="Describe this image")
model.chat(prompt="How many people are there?") # Uses same image
model.chat(prompt="What are they doing?") # Continues conversation
# Reset and start fresh
model.clear_history()
python inference/infer.py --interactive
# Commands:
# /load image.jpg - Load an image
# /describe - Describe the image
# /ocr - Extract text
# /chart - Analyze as chart
# /clear - Reset conversation
# /quit - Exit
EmberNet VLM (~300M total parameters)
β
βββ Vision Encoder: SigLIP-base (FROZEN)
β βββ Parameters: ~85M (not counted in trainable)
β βββ Input: 224Γ224 RGB image
β βββ Output: 196 tokens Γ 768 dims
β
βββ Token Compression (TRAINABLE in Stage 1)
β βββ Pixel Shuffle: 196 β 49 tokens
β βββ Adaptive Pooling: 49 β 64 tokens
β βββ Parameters: ~2M
β
βββ Projector (TRAINABLE in Stage 1)
β βββ BitLinear MLP (768 β 768 β 768)
β βββ Ternary weights {-1, 0, +1}
β βββ Parameters: ~3M
β
βββ Language Decoder: BitNet MoE (TRAINABLE in Stage 2)
βββ Layers: 16 transformer blocks
βββ Hidden size: 768
βββ Attention: GQA (12 heads, 6 KV heads) β all projections are BitLinear
βββ MoE FFN:
β βββ 8 Domain Experts (top-2 routing)
β β βββ vision_ocr, vision_diagram
β β βββ code_math_chart, code_math_formula
β β βββ spatial_reasoning (Γ2)
β β βββ agentic_reasoning (Γ2)
β βββ 1 Shared Expert (always active)
βββ All weights: Ternary {-1, 0, +1}
βββ Parameters: ~250M (50M active per forward pass)
Total Trainable: ~255M ternary parameters
Active per Forward: ~55M parameters
Model Size on Disk: <500MB
| Module | Precision | Notes |
|---|---|---|
BitNetAttention β Q/K/V/O projections | Ternary (1.58-bit) | All four projections are BitLinear |
BitNetExpert β gate / up / down projections | Ternary (1.58-bit) | All 8 domain experts + shared expert |
VisionProjector β fc1, fc2 | Ternary (1.58-bit) | BitLinear MLP in models/vision.py |
PixelShuffleCompressor β proj | FP16 | Standard nn.Linear; small (~600K params) |
AdaptivePooler β cross-attention | FP16 | Standard nn.MultiheadAttention |
RMSNorm layers (all) | FP16 | Learnable scale only; ~negligible params |
| Token embeddings + LM head (tied) | FP16 | nn.Embedding + tied nn.Linear |
MoE router (nn.Linear) | FP16 | Small (768 Γ 8); routing must stay precise |
| SigLIP vision encoder | FP16, frozen | Not counted in 255M trainable params |
| VA Refiner MLP classifier | FP32 (small) | 2-layer MLP, ~12K params; explicitly not ternary |
Summary: ~250M decoder params (+ ~1M projector) are ternary. Non-quantized components (router, layernorms, embeddings, compressor) account for β25M FP16 params. The "BitNet-b1.58-style" description is accurate for all learnable weight matrices in the decoder and projector.
BitLinear.forward() in models/bitnet_moe.py applies two Straight-Through Estimator (STE) operations:
# Activation quantization (per-token, 8-bit symmetric)
x_quant = x_norm + (activation_quant(x_norm) - x_norm).detach()
# Weight quantization (ternary: sign(W - mean(W)) * mean|W|)
w_quant = w + (weight_quant(w) - w).detach()
weight_quant() maps float weights to {-scale, 0, +scale} using the signed mean:
u = sign(w - mean(w)) * mean(|w|). The STE means gradients flow through
as if the quantization were identity β the underlying float weights are
updated continuously during training and re-quantized at each forward pass.
inference/convert.py)After training, convert_to_ternary() calls convert_bitlinear_to_ternary() on
every BitLinear module. For each:
weight_quant(module.weight) snaps weights to {-scale, 0, +scale}.pack_ternary_weights() encodes {-1, 0, +1} as {0b00, 0b01, 0b10} and
packs 4 values per byte (2 bits per weight).scale = mean(|w|) is stored alongside.TernaryLinear module pre-unpacks weights for fast inference.Non-BitLinear layers (embeddings, layernorms, router, compressor) are quantized
to INT8 via torch.quantization.quantize_dynamic in the same pass.
EmberNet/
βββ models/
β βββ bitnet_moe.py # BitLinear + MoE decoder
β βββ vision.py # SigLIP encoder + compression
β βββ va_refiner.py # VA Refiner hallucination mitigation
β βββ vlm.py # Complete VLM
βββ training/
β βββ prepare_data.py # Dataset download script
β βββ data.py # Data loading
β βββ train.py # Training loop
βββ inference/
β βββ convert.py # Model optimization
β βββ infer.py # User interface
βββ visualizations/
β βββ fig_architecture_overview.py # Fig 1: architecture + params
β βββ fig_ternary_stats.py # Fig 2: ternary weight stats
β βββ fig_moe_routing.py # Fig 3: MoE routing patterns
β βββ fig_latency_energy.py # Fig 4: latency/energy benchmark
β βββ fig_va_token_effects.py # Fig 5: VA token-level dynamics
β βββ fig_va_answer_level.py # Fig 6: VA hallucination metrics
β βββ fig_qualitative_grid.py # Fig 7: qualitative examples
β βββ ... # existing training-viz scripts
βββ generate_all_plots.py # Master visualization orchestrator
βββ requirements.txt
βββ README.md
EmberNet ships an optional Visual Absence Refiner (VA Refiner) that detects and suppresses hallucinated tokens at inference time β particularly tokens that describe visual attributes (colors, objects, counts, spatial relations) that are not grounded in the image.
The VA Refiner operates in three layers:
Neuron-level monitoring β Forward hooks are placed on shared_expert.down_proj in the BitNet MoE layers specified by va_layer_indices (default: layers 6β11). The top-K neuron activations are fed to a lightweight 2-layer MLP that predicts a per-step "visual hallucination score" p_neuron β [0, 1].
Logit discrepancy β A null baseline is computed once at the start of generation by running the decoder with image tokens zeroed out. At each step the L1 distance between the live logit distribution and the null baseline is collapsed to a score p_logit = 1 β tanh(dist / scale). High similarity to the null baseline means the model is generating as if there were no image β a sign of hallucination.
Temporal burst detection β A sliding window tracks the blended score p = Ξ±Β·p_neuron + (1βΞ±)Β·p_logit. If the window mean exceeds va_burst_threshold, a "burst" is declared and a soft log-prob penalty (va_soft_penalty) is added to every token in VISUAL_KEYWORDS until the window cools.
After generation, calibrate_answer_prefix() checks the mean VA score for the response. If it exceeds 0.70, the response is prefixed with "I cannot see that clearly, but β¦". If it exceeds 0.40, the prefix is "I might be wrong, but β¦".
# Single inference with VA Refiner
python inference/infer.py \
--model checkpoints/embernet.pt \
--image photo.jpg \
--prompt "What color is the car?" \
--use-va-refiner \
--va-threshold 0.70 \
--va-burst-threshold 0.70 \
--va-soft-penalty 5.0 \
--va-alpha 0.5
from inference.infer import EmberVLM
model = EmberVLM(
model_path="checkpoints/embernet.pt",
use_va_refiner=True,
va_threshold=0.70,
va_burst_threshold=0.70,
va_soft_penalty=5.0,
va_alpha=0.5,
)
response = model.chat(image="photo.jpg", prompt="What color is the car?")
print(response)
| Argument | Default | Description |
|---|---|---|
--use-va-refiner | off | Enable VA Refiner |
--va-threshold | 0.70 | VA score threshold per token (non-visual) |
--va-burst-threshold | 0.70 | Window mean that triggers burst mode |
--va-soft-penalty | 5.0 | Log-prob penalty during bursts |
--va-alpha | 0.5 | Blend: 1.0 = pure neuron, 0.0 = pure logit discrepancy |
Fine-grained knobs (va_layer_indices, va_neuron_k, va_window_size, va_decay_factor, va_logit_scale) can be set programmatically via VARefinerConfig in models/va_refiner.py.
va_soft_penalty < 3.0 for a lighter touch.EmberNet ships a publication-grade visualization suite in visualizations/.
Generate all figures or individual ones via generate_all_plots.py.
# Generate ALL paper figures (Figs 1β7, synthetic data, no model required)
python generate_all_plots.py --paper-only
# Generate ALL plots (training-viz + paper figures)
python generate_all_plots.py --all
# Single paper figure
python generate_all_plots.py --fig fig_ternary_stats
# With a real checkpoint for data-driven figures
python generate_all_plots.py --paper-only --model checkpoints/stage2/final_model.pt
| Figure | Script | Description |
|---|---|---|
| Fig 1 | fig_architecture_overview.py | Pipeline block diagram + parameter/bitwidth breakdown per component |
| Fig 2 | fig_ternary_stats.py | Per-layer ternary weight sparsity heatmaps and {-1,0,+1} composition bars |
| Fig 3 | fig_moe_routing.py | MoE expert routing frequency matrix across vision-language datasets |
| Fig 4 | fig_latency_energy.py | Latency, energy, and throughput: ternary vs FP16 baseline (Β±std bars) |
| Fig 5 | fig_va_token_effects.py | Per-token p_VA trajectories with burst-mode and penalisation markers |
| Fig 6 | fig_va_answer_level.py | Answer-level hallucination rate with/without VA Refiner, by visual category |
| Fig 7 | fig_qualitative_grid.py | Multi-domain qualitative panel: baseline vs VA-refined answers + p_VA sparklines |
All figures are saved as both .pdf (vector, for LaTeX) and .png (300 DPI) in plots/paper_figures/.
MIT License
165 commits
Python
100.0%
A high-quality, tiny BitNet b1.58-style Mixture-of-Experts Vision-Language Model (~300M ternary parameters) designed for edge deployment.
What it does: You give it an image + a question, it gives you an intelligent answer. OCR, chart analysis, document understanding, visual reasoning - all in <500MB.
Follow these steps in order to set up and train EmberNet:
# Navigate to project directory
cd EmberNet
# Install all required packages
pip install -r requirements.txt
Required packages:
torch>=2.0.0 - Deep learning frameworktransformers>=4.36.0 - HuggingFace transformers (for SigLIP)datasets>=2.14.0 - HuggingFace datasetswandb>=0.16.0 - Experiment trackinghuggingface_hub>=0.19.0 - HuggingFace authenticationPillow>=9.0.0 - Image processingeinops>=0.7.0 - Tensor operationsnumpy>=1.24.0 - Numerical computing2a. HuggingFace Login (REQUIRED - for downloading datasets):
# Login to HuggingFace
huggingface-cli login
# When prompted, enter your HuggingFace token
# Get your token from: https://huggingface.co/settings/tokens
# Create a token with "read" permissions
2b. Weights & Biases Login (OPTIONAL - for experiment tracking):
# Login to W&B
wandb login
# When prompted, enter your W&B API key
# Get your API key from: https://wandb.ai/authorize
# This enables training visualization and metric tracking
Choose one option based on your needs:
Option A: All Datasets (~100GB) - Best quality, all datasets
python training/prepare_data.py --all --output-dir ./data
Downloads all 20 datasets for maximum model quality.
Option B: Recommended (~70GB) - Good balance of quality and size
python training/prepare_data.py --recommended --output-dir ./data
Downloads critical + recommended datasets (excludes optional ones).
Option C: Critical Only (~50GB) - Core datasets only
python training/prepare_data.py --critical --output-dir ./data
Downloads only the most important datasets.
Option D: Minimal (~10GB) - Quick testing
python training/prepare_data.py --minimal --output-dir ./data
Downloads a tiny subset for testing the pipeline.
Other useful commands:
# List all available datasets before downloading
python training/prepare_data.py --list
# Download specific datasets only
python training/prepare_data.py --dataset textvqa chartqa vqav2 --output-dir ./data
# Explain how alignment works
python training/prepare_data.py --explain
What gets downloaded:
Note: Some datasets (ShareGPT4V, ALLaVA, ChartQA, GQA, VSR) download images from URLs, which may take longer than loading pre-packaged datasets.
What gets saved: After downloading, you'll have:
./data/{dataset_name}/ - Each dataset in its own folder./data/download_manifest.json - Central tracking file with all download info./data/dataset_index.json - Index mapping datasets to stages/domains./data/{dataset_name}/metadata.json - Individual dataset metadataThe download_manifest.json tracks:
EmberNet uses a two-stage training pipeline. Use --trial for quick validation or --main for full training.
π Quick Start: Single-Command Training
# Trial Run - Quick pipeline validation (minutes, not hours)
python training/train.py --trial --data-dir ./data
# Main Run - Full production training (hours/days)
python training/train.py --main --data-dir ./data
Both commands automatically run Stage 1 β Stage 2 sequentially.
Trial Mode (--trial)
Validates the entire pipeline with minimal data:
python training/train.py --trial --data-dir ./data
| Setting | Value |
|---|---|
| Samples per dataset | 50 |
| Epochs per stage | 1 |
| Batch size | 2 |
| Gradient accumulation | 1 |
| W&B logging | Disabled |
| Output | ./checkpoints/trial/stage{1,2}/ |
Use this to verify everything works before committing to full training.
Main Mode (--main)
Full production training with all data:
python training/train.py --main --data-dir ./data
| Setting | Stage 1 | Stage 2 |
|---|---|---|
| Samples | ALL | ALL |
| Epochs | 3 | 10 |
| Batch size | 8 | 4 |
| Gradient accumulation | 4 | 4 |
| W&B logging | Enabled | Enabled |
| Output | ./checkpoints/stage1/ | ./checkpoints/stage2/ |
Run a Specific Stage Only
# Stage 1 only (projector alignment)
python training/train.py --trial --stage 1 --data-dir ./data
# Stage 2 only (expert specialization) - requires Stage 1 checkpoint
python training/train.py --trial --stage 2 --data-dir ./data \
--resume ./checkpoints/trial/stage1/final_model.pt
Training Options
# Custom epochs and batch size
python training/train.py --main --data-dir ./data --epochs 5 --batch-size 4
# Limit samples per dataset (useful for debugging)
python training/train.py --main --data-dir ./data --max-samples-per-dataset 1000
# Disable specific features
python training/train.py --main --data-dir ./data \
--no-wandb \ # Disable W&B logging
--no-ema \ # Disable EMA
--no-curriculum \ # Disable curriculum learning
--no-adaptive-clip # Disable adaptive gradient clipping
# Hardware adjustments
python training/train.py --main --data-dir ./data \
--device cpu \ # Force CPU (slower)
--no-amp \ # Disable mixed precision
--num-workers 2 # Reduce data loading workers
Output Structure
After training completes:
checkpoints/
βββ trial/ # Trial mode outputs
β βββ stage1/
β β βββ final_model.pt # Stage 1 checkpoint
β β βββ checkpoint_epoch_1.pt
β βββ stage2/
β βββ final_model.pt # Stage 2 checkpoint (final model)
β βββ checkpoint_epoch_1.pt
β
βββ stage1/ # Main mode outputs
β βββ final_model.pt
β βββ best_model.pt
β βββ checkpoint_epoch_*.pt
β
βββ stage2/
βββ final_model.pt # β Use this for inference
βββ best_model.pt
βββ checkpoint_epoch_*.pt
What Each Stage Does
| Stage | Purpose | What Trains | What's Frozen |
|---|---|---|---|
| Stage 1 | Vision-Language Alignment | CrossModal Projector, Pooler, Compressor | Vision Encoder, LM Decoder |
| Stage 2 | Expert Specialization | MoE Router, Domain Experts | Vision Encoder, Projector, Embeddings |
Convert to optimized ternary format for deployment:
python inference/convert.py \
./checkpoints/stage2/final_model.pt \
./embernet_optimized.pt
This packs ternary weights to 2-bit representation, reducing model size to <500MB.
Interactive Mode:
python inference/infer.py \
--model ./checkpoints/stage2/final_model.pt \
--interactive
Interactive commands:
/load image.jpg - Load an image/describe - Describe the image/ocr - Extract text from image/chart - Analyze chart/graph/clear - Reset conversation/quit - ExitSingle Query:
python inference/infer.py \
--model ./checkpoints/stage2/final_model.pt \
--image photo.jpg \
--prompt "What's in this image?"
# 1. Install dependencies
cd EmberNet
pip install -r requirements.txt
# 2. Authenticate (HF required, W&B optional)
huggingface-cli login # Enter token from https://huggingface.co/settings/tokens
wandb login # Optional: Enter API key from https://wandb.ai/authorize
# 3. Download training data
python training/prepare_data.py --recommended --output-dir ./data # ~70GB
# OR: python training/prepare_data.py --all --output-dir ./data # ~100GB (best quality)
# OR: python training/prepare_data.py --minimal --output-dir ./data # ~10GB (testing only)
# 4. Train the model (SINGLE COMMAND - runs Stage 1 β Stage 2 automatically)
python training/train.py --trial --data-dir ./data # Quick test (~minutes)
# OR:
python training/train.py --main --data-dir ./data # Full training (~hours/days)
# 5. Run interactive inference
python inference/infer.py --model ./checkpoints/stage2/final_model.pt --interactive
EmberNet connects images to language through a carefully designed pipeline:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β IMAGE INPUT β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 1. VISION ENCODER (SigLIP - Frozen) β β
β β β’ Pretrained on 400M image-text pairs from Google β β
β β β’ Extracts 196 visual tokens (14Γ14 grid) β β
β β β’ Each token is a 768-dimensional feature vector β β
β β β’ Already "understands" visual concepts β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 2. TOKEN COMPRESSION (Trainable) β β
β β β’ Pixel Shuffle: 196 β 49 tokens (merges 2Γ2 neighbors) β β
β β β’ Adaptive Pooling: 49 β 64 tokens (learned queries) β β
β β β’ Preserves important visual information β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 3. PROJECTOR (BitLinear MLP) β THIS IS WHERE ALIGNMENT HAPPENS β β
β β β’ 2-layer MLP with ternary weights {-1, 0, +1} β β
β β β’ Maps: Vision embedding space β Language embedding space β β
β β β’ Trained in Stage 1 on image-caption pairs β β
β β β’ After training, visual tokens "look like" word tokens β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 4. MERGED INPUT SEQUENCE β β
β β [BOS] [IMGβ] [IMGβ] ... [IMGββ] [User: Describe this] ... β β
β β βββ visual tokens βββ βββ text tokens βββββββ β β
β β The LLM processes both as if they're all "text" β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β 5. BITNET MOE DECODER β β
β β β’ 16 transformer layers with ternary weights β β
β β β’ MoE FFN: 8 specialized experts + 1 shared expert β β
β β β’ Router sends tokens to relevant experts: β β
β β - OCR expert for text reading β β
β β - Chart expert for graphs β β
β β - Diagram expert for technical drawings β β
β β - etc. β β
β β β’ Generates response token by token β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β TEXT OUTPUT β
β "This image shows a bar chart comparing..." β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Stage 1 - Projector Alignment:
Stage 2 - Expert Specialization:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EMBERNET MoE EXPERT ARCHITECTURE β
β β
β ROUTING: Each token β TOP-2 Experts + SHARED Expert (always active) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β EXPERT 0: vision_ocr β
β ββ Specialty: Read text in images, OCR, document parsing β
β ββ Datasets: TextVQA, DocVQA, OCR-VQA β
β β
β EXPERT 1: vision_diagram β
β ββ Specialty: Understand diagrams, infographics, technical drawings β
β ββ Datasets: AI2D, InfoVQA β
β β
β EXPERT 2: code_math_chart β
β ββ Specialty: Analyze charts, graphs, plots, data visualizations β
β ββ Datasets: ChartQA, PlotQA, FigureQA, DVQA β
β β
β EXPERT 3: code_math_formula β
β ββ Specialty: Handle math equations, formulas, numerical reasoning β
β ββ Datasets: MathVista β
β β
β EXPERT 4: spatial_scene β
β ββ Specialty: Scene understanding, object detection, descriptions β
β ββ Datasets: VQAv2, Visual Genome β
β β
β EXPERT 5: spatial_reasoning β
β ββ Specialty: Spatial relationships, counting, positional reasoning β
β ββ Datasets: GQA β
β β
β EXPERT 6: agentic_knowledge β
β ββ Specialty: Knowledge-based QA, facts requiring world knowledge β
β ββ Datasets: OK-VQA, A-OKVQA β
β β
β EXPERT 7: agentic_reasoning β
β ββ Specialty: Multi-step reasoning, logic, science questions β
β ββ Datasets: ScienceQA, CLEVR β
β β
β SHARED EXPERT (Always Active) β
β ββ Specialty: Common patterns, language generation, general knowledge β
β ββ Datasets: ALL datasets (learns shared representations) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Example Routing:
User: "What does this chart show? The title says Q3 Sales."
β
ββ> EXPERT 0 (vision_ocr) - reads "Q3 Sales" text
ββ> EXPERT 2 (chart) - analyzes chart structure
ββ> SHARED EXPERT - general language/context
Combined output: "This bar chart shows Q3 sales data..."
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| LLaVA-Instruct-150K β | lmms-lab/LLaVA-Instruct-150K | GPT-4 generated visual conversations | 150K | ~5GB |
| ShareGPT4V β | lmms-lab/ShareGPT4V | Detailed image descriptions from GPT-4V | 100K | ~8GB |
| ALLaVA | FreedomIntelligence/ALLaVA-4V | Diverse visual instructions | 711K | ~6GB |
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| TextVQA β | lmms-lab/TextVQA | Text in natural scenes | 45K | ~2GB |
| DocVQA β | lmms-lab/DocVQA | Documents, forms, receipts | 50K | ~3GB |
| AI2D β | lmms-lab/ai2d | Scientific diagrams | 15K | ~1.5GB |
| InfoVQA | lmms-lab/InfographicVQA | Infographics | 30K | ~2.5GB |
| OCR-VQA | howard-hou/OCR-VQA | Book covers, signs | 200K | ~4GB |
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| ChartQA β | ahmed-masry/ChartQA | Bar, line, pie charts | 32K | ~1GB |
| MathVista β | AI4Math/MathVista | Mathematical visual reasoning | 6K | ~1GB |
| PlotQA | lmms-lab/PlotQA | Scientific plots | 224K | ~8GB |
| FigureQA | lmms-lab/FigureQA | Figure understanding | 180K | ~5GB |
| DVQA | lmms-lab/DVQA | Data visualization | 300K | ~3GB |
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| VQAv2 β | lmms-lab/VQAv2 | General visual QA | 1.1M | ~25GB |
| GQA | lmms-lab/GQA | Scene graph reasoning | 22M | ~15GB |
| Visual Genome | lmms-lab/VisualGenome | Dense scene annotations | 108K | ~15GB |
| Dataset | HuggingFace ID | Description | Samples | Size |
|---|---|---|---|---|
| ScienceQA β | derek-thomas/ScienceQA | Science with diagrams | 21K | ~2GB |
| OK-VQA | lmms-lab/OK-VQA | Outside knowledge VQA | 14K | ~1GB |
| A-OKVQA | lmms-lab/A-OKVQA | Augmented knowledge VQA | 25K | ~1.5GB |
| CLEVR | lmms-lab/CLEVR | Compositional reasoning | 850K | ~18GB |
β = Critical (included in --critical mode)
# Minimal (~10GB) - For quick testing only
python training/prepare_data.py --minimal
# Includes: llava_instruct_150k, textvqa, chartqa, vqav2
# Critical (~45GB) - Essential for a working model
python training/prepare_data.py --critical
# Includes: All β
marked datasets above
# Recommended (~70GB) - Good quality model
python training/prepare_data.py --recommended
# Includes: Critical + recommended datasets
# All (~100GB) - Maximum quality
python training/prepare_data.py --all
# Includes: Everything
| Stage | Minimum | Recommended | Notes |
|---|---|---|---|
| Stage 1 | 8GB VRAM | 16GB VRAM | Can use CPU with 16GB RAM (slower) |
| Stage 2 | 12GB VRAM | 24GB VRAM | Can use CPU with 32GB RAM (slower) |
| Stage | GPU (A100) | GPU (RTX 3090) | CPU (32 cores) |
|---|---|---|---|
| Stage 1 (3 epochs) | 6-12 hours | 12-24 hours | 3-5 days |
| Stage 2 (10 epochs) | 24-48 hours | 48-96 hours | 10-15 days |
Basic Training (Recommended):
# Stage 1: Projector Alignment
python training/train.py \
--stage 1 \
--data-dir ./data \
--epochs 3 \
--batch-size 8 \
--output-dir ./checkpoints
# Stage 2: Expert Specialization
python training/train.py \
--stage 2 \
--data-dir ./data \
--epochs 10 \
--batch-size 4 \
--resume ./checkpoints/checkpoint_epoch_3.pt \
--output-dir ./checkpoints
With W&B Logging (Recommended):
# Stage 1
python training/train.py \
--stage 1 \
--data-dir ./data \
--epochs 3 \
--batch-size 8 \
--wandb-project EmberNet \
--wandb-run-name stage1_projector
# Stage 2
python training/train.py \
--stage 2 \
--data-dir ./data \
--epochs 10 \
--batch-size 4 \
--resume ./checkpoints/checkpoint_epoch_3.pt \
--wandb-project EmberNet \
--wandb-run-name stage2_experts
Custom Learning Rate:
python training/train.py \
--stage 1 \
--data-dir ./data \
--epochs 3 \
--lr 5e-4
Resume from Checkpoint:
python training/train.py \
--stage 2 \
--data-dir ./data \
--resume ./checkpoints/checkpoint_epoch_5.pt
Out of Memory (OOM) Errors:
# Reduce batch size and increase gradient accumulation
python training/train.py \
--stage 1 \
--data-dir ./data \
--batch-size 2 \
--grad-accum 16
# Effective batch size = 2 * 16 = 32
Slow Training:
# Check if GPU is being used
python training/train.py --stage 1 --data-dir ./data --device cuda
# Increase number of data loading workers
python training/train.py --stage 1 --data-dir ./data --num-workers 8
Mixed Precision Issues:
# Disable automatic mixed precision
python training/train.py --stage 1 --data-dir ./data --no-amp
CPU Training:
# Force CPU (much slower but works without GPU)
python training/train.py --stage 1 --data-dir ./data --device cpu --batch-size 1
Training Settings:
--stage {1,2} Training stage (1=projector, 2=expert SFT)
--epochs N Number of training epochs (default: 3)
--batch-size N Training batch size (default: 4)
--lr LR Learning rate (auto-set: 1e-3 for stage1, 1e-4 for stage2)
--grad-accum N Gradient accumulation steps (default: 4)
Paths:
--data-dir DIR Data directory (default: ./data)
--output-dir DIR Output directory for checkpoints (default: ./checkpoints)
--resume PATH Resume from checkpoint
Hardware:
--device DEVICE Device: cuda/cpu/auto (default: auto)
--no-amp Disable mixed precision training
--num-workers N Data loading workers (default: 4)
Experiment Tracking:
--wandb Use W&B logging (default: True)
--no-wandb Disable W&B logging
--wandb-project NAME W&B project name (default: EmberNet)
--wandb-run-name NAME W&B run name (default: auto-generated)
During training, the following checkpoints are saved to --output-dir:
checkpoint_epoch_N.pt - Saved after each epochcheckpoint_step_N.pt - Saved every N steps (configurable)best_model.pt - Best model based on validation lossfinal_model.pt - Final model after all epochsEach checkpoint contains:
With Weights & Biases: Visit https://wandb.ai/your-username/EmberNet to see:
Console Output:
======================================================================
Starting Stage 1 Training
======================================================================
Epochs: 3
Steps per epoch: 1000
Total steps: 3000
Batch size: 8
Gradient accumulation: 4
Effective batch size: 32
Learning rate: 0.001
Device: cuda
--- Token Statistics (per sample) ---
Total tokens: 2,048
ββ Image tokens: 64
ββ Text tokens: 1,984
--- Token Statistics (per batch) ---
Total tokens: 16,384
ββ Image tokens: 512
ββ Text tokens: 15,872
--- Total Training Tokens (all epochs) ---
Total tokens: 49,152,000
ββ Image tokens: 1,536,000
ββ Text tokens: 47,616,000
======================================================================
Epoch 1/3 | Step 100 | Loss: 2.3456 | Avg Loss: 2.4567 | LR: 1.00e-03
from inference.infer import EmberVLM
# Load model
model = EmberVLM("checkpoints/embernet.pt")
# Basic image Q&A
response = model.chat(
image="photo.jpg",
prompt="What's in this image?"
)
print(response)
# OCR - Read text from image
response = model.chat(
image="document.png",
prompt="Extract all text from this document"
)
# Chart analysis
response = model.chat(
image="chart.png",
prompt="What does this chart show? What's the maximum value?"
)
# Multi-turn conversation (remembers context)
model.chat(image="scene.jpg", prompt="Describe this image")
model.chat(prompt="How many people are there?") # Uses same image
model.chat(prompt="What are they doing?") # Continues conversation
# Reset and start fresh
model.clear_history()
python inference/infer.py --interactive
# Commands:
# /load image.jpg - Load an image
# /describe - Describe the image
# /ocr - Extract text
# /chart - Analyze as chart
# /clear - Reset conversation
# /quit - Exit
EmberNet VLM (~300M total parameters)
β
βββ Vision Encoder: SigLIP-base (FROZEN)
β βββ Parameters: ~85M (not counted in trainable)
β βββ Input: 224Γ224 RGB image
β βββ Output: 196 tokens Γ 768 dims
β
βββ Token Compression (TRAINABLE in Stage 1)
β βββ Pixel Shuffle: 196 β 49 tokens
β βββ Adaptive Pooling: 49 β 64 tokens
β βββ Parameters: ~2M
β
βββ Projector (TRAINABLE in Stage 1)
β βββ BitLinear MLP (768 β 768 β 768)
β βββ Ternary weights {-1, 0, +1}
β βββ Parameters: ~3M
β
βββ Language Decoder: BitNet MoE (TRAINABLE in Stage 2)
βββ Layers: 16 transformer blocks
βββ Hidden size: 768
βββ Attention: GQA (12 heads, 6 KV heads) β all projections are BitLinear
βββ MoE FFN:
β βββ 8 Domain Experts (top-2 routing)
β β βββ vision_ocr, vision_diagram
β β βββ code_math_chart, code_math_formula
β β βββ spatial_reasoning (Γ2)
β β βββ agentic_reasoning (Γ2)
β βββ 1 Shared Expert (always active)
βββ All weights: Ternary {-1, 0, +1}
βββ Parameters: ~250M (50M active per forward pass)
Total Trainable: ~255M ternary parameters
Active per Forward: ~55M parameters
Model Size on Disk: <500MB
| Module | Precision | Notes |
|---|---|---|
BitNetAttention β Q/K/V/O projections | Ternary (1.58-bit) | All four projections are BitLinear |
BitNetExpert β gate / up / down projections | Ternary (1.58-bit) | All 8 domain experts + shared expert |
VisionProjector β fc1, fc2 | Ternary (1.58-bit) | BitLinear MLP in models/vision.py |
PixelShuffleCompressor β proj | FP16 | Standard nn.Linear; small (~600K params) |
AdaptivePooler β cross-attention | FP16 | Standard nn.MultiheadAttention |
RMSNorm layers (all) | FP16 | Learnable scale only; ~negligible params |
| Token embeddings + LM head (tied) | FP16 | nn.Embedding + tied nn.Linear |
MoE router (nn.Linear) | FP16 | Small (768 Γ 8); routing must stay precise |
| SigLIP vision encoder | FP16, frozen | Not counted in 255M trainable params |
| VA Refiner MLP classifier | FP32 (small) | 2-layer MLP, ~12K params; explicitly not ternary |
Summary: ~250M decoder params (+ ~1M projector) are ternary. Non-quantized components (router, layernorms, embeddings, compressor) account for β25M FP16 params. The "BitNet-b1.58-style" description is accurate for all learnable weight matrices in the decoder and projector.
BitLinear.forward() in models/bitnet_moe.py applies two Straight-Through Estimator (STE) operations:
# Activation quantization (per-token, 8-bit symmetric)
x_quant = x_norm + (activation_quant(x_norm) - x_norm).detach()
# Weight quantization (ternary: sign(W - mean(W)) * mean|W|)
w_quant = w + (weight_quant(w) - w).detach()
weight_quant() maps float weights to {-scale, 0, +scale} using the signed mean:
u = sign(w - mean(w)) * mean(|w|). The STE means gradients flow through
as if the quantization were identity β the underlying float weights are
updated continuously during training and re-quantized at each forward pass.
inference/convert.py)After training, convert_to_ternary() calls convert_bitlinear_to_ternary() on
every BitLinear module. For each:
weight_quant(module.weight) snaps weights to {-scale, 0, +scale}.pack_ternary_weights() encodes {-1, 0, +1} as {0b00, 0b01, 0b10} and
packs 4 values per byte (2 bits per weight).scale = mean(|w|) is stored alongside.TernaryLinear module pre-unpacks weights for fast inference.Non-BitLinear layers (embeddings, layernorms, router, compressor) are quantized
to INT8 via torch.quantization.quantize_dynamic in the same pass.
EmberNet/
βββ models/
β βββ bitnet_moe.py # BitLinear + MoE decoder
β βββ vision.py # SigLIP encoder + compression
β βββ va_refiner.py # VA Refiner hallucination mitigation
β βββ vlm.py # Complete VLM
βββ training/
β βββ prepare_data.py # Dataset download script
β βββ data.py # Data loading
β βββ train.py # Training loop
βββ inference/
β βββ convert.py # Model optimization
β βββ infer.py # User interface
βββ visualizations/
β βββ fig_architecture_overview.py # Fig 1: architecture + params
β βββ fig_ternary_stats.py # Fig 2: ternary weight stats
β βββ fig_moe_routing.py # Fig 3: MoE routing patterns
β βββ fig_latency_energy.py # Fig 4: latency/energy benchmark
β βββ fig_va_token_effects.py # Fig 5: VA token-level dynamics
β βββ fig_va_answer_level.py # Fig 6: VA hallucination metrics
β βββ fig_qualitative_grid.py # Fig 7: qualitative examples
β βββ ... # existing training-viz scripts
βββ generate_all_plots.py # Master visualization orchestrator
βββ requirements.txt
βββ README.md
EmberNet ships an optional Visual Absence Refiner (VA Refiner) that detects and suppresses hallucinated tokens at inference time β particularly tokens that describe visual attributes (colors, objects, counts, spatial relations) that are not grounded in the image.
The VA Refiner operates in three layers:
Neuron-level monitoring β Forward hooks are placed on shared_expert.down_proj in the BitNet MoE layers specified by va_layer_indices (default: layers 6β11). The top-K neuron activations are fed to a lightweight 2-layer MLP that predicts a per-step "visual hallucination score" p_neuron β [0, 1].
Logit discrepancy β A null baseline is computed once at the start of generation by running the decoder with image tokens zeroed out. At each step the L1 distance between the live logit distribution and the null baseline is collapsed to a score p_logit = 1 β tanh(dist / scale). High similarity to the null baseline means the model is generating as if there were no image β a sign of hallucination.
Temporal burst detection β A sliding window tracks the blended score p = Ξ±Β·p_neuron + (1βΞ±)Β·p_logit. If the window mean exceeds va_burst_threshold, a "burst" is declared and a soft log-prob penalty (va_soft_penalty) is added to every token in VISUAL_KEYWORDS until the window cools.
After generation, calibrate_answer_prefix() checks the mean VA score for the response. If it exceeds 0.70, the response is prefixed with "I cannot see that clearly, but β¦". If it exceeds 0.40, the prefix is "I might be wrong, but β¦".
# Single inference with VA Refiner
python inference/infer.py \
--model checkpoints/embernet.pt \
--image photo.jpg \
--prompt "What color is the car?" \
--use-va-refiner \
--va-threshold 0.70 \
--va-burst-threshold 0.70 \
--va-soft-penalty 5.0 \
--va-alpha 0.5
from inference.infer import EmberVLM
model = EmberVLM(
model_path="checkpoints/embernet.pt",
use_va_refiner=True,
va_threshold=0.70,
va_burst_threshold=0.70,
va_soft_penalty=5.0,
va_alpha=0.5,
)
response = model.chat(image="photo.jpg", prompt="What color is the car?")
print(response)
| Argument | Default | Description |
|---|---|---|
--use-va-refiner | off | Enable VA Refiner |
--va-threshold | 0.70 | VA score threshold per token (non-visual) |
--va-burst-threshold | 0.70 | Window mean that triggers burst mode |
--va-soft-penalty | 5.0 | Log-prob penalty during bursts |
--va-alpha | 0.5 | Blend: 1.0 = pure neuron, 0.0 = pure logit discrepancy |
Fine-grained knobs (va_layer_indices, va_neuron_k, va_window_size, va_decay_factor, va_logit_scale) can be set programmatically via VARefinerConfig in models/va_refiner.py.
va_soft_penalty < 3.0 for a lighter touch.EmberNet ships a publication-grade visualization suite in visualizations/.
Generate all figures or individual ones via generate_all_plots.py.
# Generate ALL paper figures (Figs 1β7, synthetic data, no model required)
python generate_all_plots.py --paper-only
# Generate ALL plots (training-viz + paper figures)
python generate_all_plots.py --all
# Single paper figure
python generate_all_plots.py --fig fig_ternary_stats
# With a real checkpoint for data-driven figures
python generate_all_plots.py --paper-only --model checkpoints/stage2/final_model.pt
| Figure | Script | Description |
|---|---|---|
| Fig 1 | fig_architecture_overview.py | Pipeline block diagram + parameter/bitwidth breakdown per component |
| Fig 2 | fig_ternary_stats.py | Per-layer ternary weight sparsity heatmaps and {-1,0,+1} composition bars |
| Fig 3 | fig_moe_routing.py | MoE expert routing frequency matrix across vision-language datasets |
| Fig 4 | fig_latency_energy.py | Latency, energy, and throughput: ternary vs FP16 baseline (Β±std bars) |
| Fig 5 | fig_va_token_effects.py | Per-token p_VA trajectories with burst-mode and penalisation markers |
| Fig 6 | fig_va_answer_level.py | Answer-level hallucination rate with/without VA Refiner, by visual category |
| Fig 7 | fig_qualitative_grid.py | Multi-domain qualitative panel: baseline vs VA-refined answers + p_VA sparklines |
All figures are saved as both .pdf (vector, for LaTeX) and .png (300 DPI) in plots/paper_figures/.
MIT License
165 commits
Python
100.0%