LittleBitLLM commited on
Commit
3dfac2f
·
verified ·
1 Parent(s): 99e15ba

LittleBit: recipe and throughput (weights coming soon)

Browse files
README.md ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-4.0
3
+ base_model: Qwen/Qwen3-8B
4
+ base_model_relation: quantized
5
+ library_name: transformers
6
+ tags: [littlebit, littlebit-2, sub-1-bit, quantization, qat, qwen3]
7
+ ---
8
+
9
+ ![LittleBit.](assets/banner.png)
10
+
11
+ # LittleBit
12
+
13
+ **Big models. Little bits.** Sub-1-bit Qwen3 models built with LittleBit-2:
14
+ binarized latent factorization, recovered by distillation from the original model.
15
+
16
+ > **Status: weights coming soon.** This repo holds the training recipe and measured
17
+ > throughput. Model weights and eval results will be added here when the first full
18
+ > training run finishes. Follow [@LittleBit_llm](https://x.com/LittleBit_llm) for updates.
19
+
20
+ > Independent community project. Not affiliated with or endorsed by Samsung Research.
21
+ > Built on the LittleBit method and code by Lee, Kim, You & Kim ([SamsungLabs/LittleBit](https://github.com/SamsungLabs/LittleBit)).
22
+
23
+ ## Planned releases
24
+
25
+ | Model | Base | Target bpw | Status |
26
+ |---|---|---|---|
27
+ | littlebit-qwen3-4b | Qwen/Qwen3-4B | 0.55 | planned |
28
+ | littlebit-qwen3-8b | Qwen/Qwen3-8B | 0.55 | planned |
29
+ | littlebit-qwen3-14b | Qwen/Qwen3-14B | 0.55 | planned |
30
+
31
+ Bits per weight apply to linear layers. Embeddings and `lm_head` stay BF16.
32
+
33
+ ## Method
34
+
35
+ Each linear layer `W` is approximated as `sign(U) · diag(h·g·ℓ) · sign(V)ᵀ`:
36
+ low-rank latent factors binarized to ±1, plus three thin learned scale vectors.
37
+
38
+ 1. **Latent factorization:** SVD splits each linear layer into rank-r factors sized to the bit budget.
39
+ 2. **Joint-ITQ rotation (LittleBit-2):** aligns the factors with the binary hypercube before training. It folds into the factors, so it adds no inference cost.
40
+ 3. **SmoothSign binarization:** a smooth surrogate gradient keeps the sign step trainable.
41
+ 4. **Residual compensation:** a second binarized path learns what the first one missed.
42
+ 5. **Distillation:** quantization-aware training on C4 + WikiText-2 (seq len 2048), with the BF16 model as teacher (logit KL + layer-to-layer MSE).
43
+
44
+ ## Measured throughput
45
+
46
+ Measured on RunPod with the recipe in [`recipe/`](recipe/). One step = 4 sequences × 2048 tokens.
47
+ One epoch of the C4-shard-0 + WikiText-2 mix is about 20,750 steps with the Qwen3 tokenizer.
48
+
49
+ | Model | GPU | Sec / step | Peak VRAM | One-time init (SVD + Joint-ITQ) | Est. 1 epoch |
50
+ |---|---|---:|---:|---:|---:|
51
+ | Qwen3-0.6B @ 0.55 bpw | 1× H100 80GB | 1.03 | — | ~2.3 min | ~6 h |
52
+ | Qwen3-8B @ 0.55 bpw | 1× H200 141GB | 2.84 | ~107 GB | ~14 min | ~16.5 h |
53
+
54
+ Qwen3-8B does not fit on a single 80 GB GPU: about 3.7B latent parameters are trainable,
55
+ and their optimizer state alone exceeds the memory. Use a 141 GB GPU or ≥ 2 GPUs with ZeRO-3.
56
+
57
+ ## Recipe
58
+
59
+ [`recipe/`](recipe/) runs the official LittleBit code on a RunPod GPU pod:
60
+
61
+ - `setup.sh`: clones SamsungLabs/LittleBit at a pinned commit, applies the patches, and installs dependencies (`transformers==4.51.*`, DeepSpeed).
62
+ - `train.sh`: runs QAT. Defaults: Qwen3-8B, 0.55 bpw, LittleBit-2 init, SmoothSign, residual. Override settings with env vars (`MODEL_ID`, `EFF_BIT`, `EPOCHS`, `NUM_GPUS`, …).
63
+ - `eval.sh`: measures WikiText-2/C4 perplexity and zero-shot accuracy (lm-eval).
64
+ - `zero3_nooffload.json`: multi-GPU ZeRO-3 config without CPU offload.
65
+ - `patches/teacher-on-gpu.patch`: keeps the teacher on GPU instead of ZeRO-3 CPU offload (`--teacher_offload False`).
66
+ - `patches/eval-import-fix.patch`: fixes a circular import between lm-eval, transformers, and DeepSpeed in `eval.py`.
67
+
68
+ ```bash
69
+ bash recipe/setup.sh
70
+ MODEL_ID=Qwen/Qwen3-8B EFF_BIT=0.55 EPOCHS=1 bash recipe/train.sh
71
+ CKPT=/workspace/outputs/littlebit-qwen3-8b-0.55bpw bash recipe/eval.sh
72
+ ```
73
+
74
+ ## License
75
+
76
+ CC BY-NC 4.0 (non-commercial), inherited from the LittleBit code. Released weights
77
+ are also subject to the base model's license (Qwen3: Apache 2.0).
78
+
79
+ ## Citation
80
+
81
+ ```bibtex
82
+ @inproceedings{lee2025littlebit,
83
+ title = {LittleBit: Ultra Low-Bit Quantization via Latent Factorization},
84
+ author = {Lee, Banseok and Kim, Dongkyu and You, Youngcheon and Kim, Youngmin},
85
+ booktitle = {NeurIPS},
86
+ year = {2025}
87
+ }
88
+ @inproceedings{lee2026littlebit2,
89
+ title = {LittleBit-2: Maximizing the Spectral Energy Gain in Sub-1-Bit LLMs via Latent Geometry Alignment},
90
+ author = {Lee, Banseok and Kim, Youngmin},
91
+ booktitle = {ICML},
92
+ year = {2026}
93
+ }
94
+ ```
assets/banner.png ADDED
recipe/eval.sh ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Evaluate a trained LittleBit checkpoint (perplexity + zero-shot).
3
+ # CKPT=/workspace/outputs/Qwen2.5-0.5B-1.0bpw bash eval.sh
4
+ # Set FP_BASELINE=1 with CKPT=<hf model id> to score the original FP16 model.
5
+ set -euo pipefail
6
+
7
+ WORKDIR="${WORKDIR:-/workspace}"
8
+ CKPT="${CKPT:?set CKPT to a checkpoint dir or HF model id}"
9
+ ZEROSHOT="${ZEROSHOT:-boolq,piqa,hellaswag,winogrande,arc_easy,arc_challenge,openbookqa}"
10
+
11
+ export HF_HOME="$WORKDIR/hf_cache"
12
+ # shellcheck disable=SC1091
13
+ source "$WORKDIR/venv/bin/activate"
14
+ cd "$WORKDIR/LittleBit"
15
+ mkdir -p "$WORKDIR/logs"
16
+
17
+ EXTRA=()
18
+ if [ "${FP_BASELINE:-0}" = "1" ]; then EXTRA+=(--fp_eval True); fi
19
+
20
+ CUDA_VISIBLE_DEVICES=0 python eval.py \
21
+ --model_id "$CKPT" \
22
+ --seqlen 2048 \
23
+ --ppl_task wikitext2,c4 \
24
+ --zeroshot_task "$ZEROSHOT" \
25
+ "${EXTRA[@]}" 2>&1 | tee "$WORKDIR/logs/eval-$(basename "$CKPT")-$(date +%Y%m%d-%H%M).log"
recipe/patches/eval-import-fix.patch ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ diff --git a/eval.py b/eval.py
2
+ index 446b1ed..84b3355 100644
3
+ --- a/eval.py
4
+ +++ b/eval.py
5
+ @@ -4,6 +4,15 @@ import json
6
+ import torch
7
+ import torch._dynamo
8
+ import torch.nn as nn
9
+ +
10
+ +# Import deepspeed before lm_eval/transformers (as main.py does). Otherwise
11
+ +# transformers.modeling_utils imports deepspeed mid-init and deepspeed's
12
+ +# import of transformers.models.opt hits a circular import.
13
+ +try:
14
+ + import deepspeed # noqa: F401
15
+ +except ImportError:
16
+ + pass
17
+ +
18
+ from lm_eval import evaluator
19
+ from tqdm import tqdm
20
+ from transformers import AutoTokenizer, AutoModelForCausalLM
recipe/patches/teacher-on-gpu.patch ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ diff --git a/main.py b/main.py
2
+ index d5f6810..d30ecb2 100644
3
+ --- a/main.py
4
+ +++ b/main.py
5
+ @@ -83,6 +83,9 @@ def get_args():
6
+
7
+ parser.add_argument("--use_itq", type=str2bool, default=False)
8
+ parser.add_argument("--itq_n_iter", type=int, default=50)
9
+ + parser.add_argument("--teacher_offload", type=str2bool, default=True,
10
+ + help="True: teacher via DeepSpeed ZeRO-3 with CPU param offload (upstream). "
11
+ + "False: full bf16 teacher copy resident on each GPU (faster, needs VRAM).")
12
+
13
+ args = parser.parse_args()
14
+
15
+ @@ -142,7 +145,29 @@ def load_student_model(args, device_map, torch_dtype):
16
+ return model
17
+
18
+
19
+ +def load_teacher_on_gpu(args, torch_dtype):
20
+ + local_rank = int(os.environ.get("LOCAL_RANK", 0))
21
+ + device = torch.device(f"cuda:{local_rank}")
22
+ +
23
+ + teacher_model = AutoModelForCausalLM.from_pretrained(
24
+ + args.model_id,
25
+ + torch_dtype=torch_dtype,
26
+ + low_cpu_mem_usage=True,
27
+ + trust_remote_code=True,
28
+ + ).to(device)
29
+ + teacher_model.eval()
30
+ + for param in teacher_model.parameters():
31
+ + param.requires_grad = False
32
+ + teacher_model.config.use_cache = False
33
+ +
34
+ + logger.info(f"Teacher resident on {device} (no offload)")
35
+ + return teacher_model
36
+ +
37
+ +
38
+ def load_teacher_model(args, num_gpus, torch_dtype, config_path="configs/zero3_inference.json"):
39
+ + if not args.teacher_offload:
40
+ + return load_teacher_on_gpu(args, torch_dtype)
41
+ +
42
+ with open(config_path, 'r') as f:
43
+ config = json.load(f)
44
+
recipe/setup.sh ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # One-time setup for LittleBit on a RunPod GPU pod.
3
+ # Run from the pod terminal: bash /workspace/runpod/setup.sh
4
+ set -euo pipefail
5
+
6
+ WORKDIR="${WORKDIR:-/workspace}"
7
+ REPO_DIR="$WORKDIR/LittleBit"
8
+ VENV_DIR="$WORKDIR/venv"
9
+
10
+ # Keep every cache on the persistent volume so it survives pod restarts.
11
+ export HF_HOME="$WORKDIR/hf_cache"
12
+ export PIP_CACHE_DIR="$WORKDIR/pip_cache"
13
+
14
+ echo ">> GPU check"
15
+ nvidia-smi --query-gpu=name,memory.total --format=csv || { echo "No NVIDIA GPU found"; exit 1; }
16
+
17
+ echo ">> System packages (mpi4py needs OpenMPI headers)"
18
+ apt-get update -qq && apt-get install -y -qq libopenmpi-dev git >/dev/null
19
+
20
+ if [ ! -d "$REPO_DIR" ]; then
21
+ echo ">> Cloning LittleBit"
22
+ git clone https://github.com/SamsungLabs/LittleBit.git "$REPO_DIR"
23
+ # Pin the commit the patches were written against.
24
+ git -C "$REPO_DIR" checkout -q 42d658b
25
+ fi
26
+
27
+ echo ">> Applying local patches"
28
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29
+ for p in "$SCRIPT_DIR"/patches/*.patch; do
30
+ if git -C "$REPO_DIR" apply --reverse --check "$p" 2>/dev/null; then
31
+ echo " already applied: $(basename "$p")"
32
+ else
33
+ git -C "$REPO_DIR" apply "$p" && echo " applied: $(basename "$p")"
34
+ fi
35
+ done
36
+
37
+ echo ">> Python venv (inherits the pod's preinstalled CUDA torch)"
38
+ python3 -m venv --system-site-packages "$VENV_DIR"
39
+ # shellcheck disable=SC1091
40
+ source "$VENV_DIR/bin/activate"
41
+ pip install -q --upgrade pip
42
+
43
+ # requirements.txt pins torch==2.6.0, which would replace the pod's CUDA build.
44
+ # Install everything except torch, then the versions the README asks for.
45
+ grep -viE '^torch(==|$)' "$REPO_DIR/requirements.txt" > /tmp/req-notorch.txt
46
+ pip install -q -r /tmp/req-notorch.txt
47
+ pip install -q "transformers==4.51.*" deepspeed wandb
48
+
49
+ echo ">> Sanity check"
50
+ python - <<'PY'
51
+ import torch, transformers, deepspeed
52
+ print("torch", torch.__version__, "cuda", torch.version.cuda, "available", torch.cuda.is_available())
53
+ print("transformers", transformers.__version__, "deepspeed", deepspeed.__version__)
54
+ for i in range(torch.cuda.device_count()):
55
+ print(" gpu", i, torch.cuda.get_device_name(i))
56
+ assert torch.cuda.is_available(), "CUDA not available"
57
+ PY
58
+
59
+ cat <<EOF
60
+
61
+ Setup done. Next:
62
+ export HF_TOKEN=hf_xxx # needed for gated models like Llama-2
63
+ export WANDB_API_KEY=xxx # optional; without it, REPORT=tensorboard is used
64
+ bash $WORKDIR/runpod/train.sh
65
+ EOF
recipe/train.sh ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # LittleBit QAT training on RunPod. Override any setting via env vars, e.g.
3
+ # MODEL_ID=Qwen/Qwen3-14B EFF_BIT=0.55 EPOCHS=5 bash train.sh
4
+ set -euo pipefail
5
+
6
+ WORKDIR="${WORKDIR:-/workspace}"
7
+ REPO_DIR="$WORKDIR/LittleBit"
8
+
9
+ MODEL_ID="${MODEL_ID:-Qwen/Qwen3-8B}"
10
+ EFF_BIT="${EFF_BIT:-0.55}" # bits per weight: 1.0, 0.55, 0.3, 0.1 ...
11
+ EPOCHS="${EPOCHS:-1.0}" # paper uses 5.0
12
+ BATCH="${BATCH:-2}" # Qwen3's 151k vocab makes KD logits large
13
+ GRAD_ACCUM="${GRAD_ACCUM:-2}"
14
+ LR="${LR:-4e-05}"
15
+ USE_ITQ="${USE_ITQ:-True}" # True = LittleBit-2 init (Joint-ITQ)
16
+ TEACHER_OFFLOAD="${TEACHER_OFFLOAD:-False}" # False = teacher stays on GPU (faster, patched)
17
+ NUM_GPUS="${NUM_GPUS:-$(nvidia-smi -L | wc -l)}"
18
+ MODEL_SLUG="$(basename "$MODEL_ID" | tr '[:upper:]' '[:lower:]')"
19
+ RUN_NAME="${RUN_NAME:-littlebit-${MODEL_SLUG}-${EFF_BIT}bpw}"
20
+ SAVE_DIR="${SAVE_DIR:-$WORKDIR/outputs}"
21
+
22
+ if [ -z "${REPORT:-}" ]; then
23
+ if [ -n "${WANDB_API_KEY:-}" ]; then REPORT=wandb; else REPORT=tensorboard; fi
24
+ fi
25
+
26
+ export HF_HOME="$WORKDIR/hf_cache"
27
+ # shellcheck disable=SC1091
28
+ source "$WORKDIR/venv/bin/activate"
29
+ cd "$REPO_DIR"
30
+ mkdir -p "$WORKDIR/logs"
31
+ LOG="$WORKDIR/logs/train-$RUN_NAME-$(date +%Y%m%d-%H%M).log"
32
+
33
+ ARGS=(
34
+ --model_id "$MODEL_ID"
35
+ --dataset c4_wiki
36
+ --data_root "$WORKDIR/data"
37
+ --save_dir "$SAVE_DIR"
38
+ --f_name "$RUN_NAME"
39
+ --run_name "$RUN_NAME"
40
+ --num_train_epochs "$EPOCHS"
41
+ --per_device_train_batch_size "$BATCH"
42
+ --gradient_accumulation_steps "$GRAD_ACCUM"
43
+ --lr "$LR"
44
+ --warmup_ratio 0.02
45
+ --report "$REPORT"
46
+ --quant_func SmoothSign
47
+ --quant_mod LittleBitLinear
48
+ --residual True
49
+ --eff_bit "$EFF_BIT"
50
+ --kv_factor 1.0
51
+ --min_split_dim 8
52
+ --l2l_loss_scale 10.0
53
+ --use_itq "$USE_ITQ"
54
+ --teacher_offload "$TEACHER_OFFLOAD"
55
+ )
56
+ # Multi-GPU: ZeRO-3 sharding. Default keeps optimizer/params on GPU (fast);
57
+ # set DS_CONFIG=configs/zero3.json for upstream CPU offload (slow, less VRAM).
58
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
59
+ DS_CONFIG="${DS_CONFIG:-$SCRIPT_DIR/zero3_nooffload.json}"
60
+ if [ "$NUM_GPUS" -gt 1 ]; then
61
+ ARGS+=(--ds_config_path "$DS_CONFIG")
62
+ fi
63
+
64
+ # Always go through the deepspeed launcher: it sets LOCAL_RANK, which main.py
65
+ # needs to place the student on a concrete GPU (plain `python -m main` passes
66
+ # device_map="auto" to model.to(), which fails).
67
+ echo ">> $RUN_NAME on $NUM_GPUS GPU(s), log: $LOG"
68
+ deepspeed --num_gpus="$NUM_GPUS" main.py "${ARGS[@]}" 2>&1 | tee "$LOG"
69
+
70
+ echo ">> Checkpoint: $SAVE_DIR/$RUN_NAME"
recipe/zero3_nooffload.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bf16": {
3
+ "enabled": true
4
+ },
5
+
6
+ "optimizer": {
7
+ "type": "AdamW",
8
+ "params": {
9
+ "lr": "auto",
10
+ "betas": "auto",
11
+ "eps": "auto",
12
+ "weight_decay": "auto"
13
+ }
14
+ },
15
+
16
+ "scheduler": {
17
+ "type": "WarmupCosineLR",
18
+ "params": {
19
+ "total_num_steps": "auto",
20
+ "warmup_min_ratio": 0,
21
+ "warmup_num_steps": "auto",
22
+ "cos_min_ratio": 0.0001,
23
+ "warmup_type": "linear"
24
+ }
25
+ },
26
+
27
+ "zero_optimization": {
28
+ "stage": 3,
29
+ "overlap_comm": true,
30
+ "contiguous_gradients": true,
31
+ "sub_group_size": 1e9,
32
+ "reduce_bucket_size": 5e8,
33
+ "stage3_prefetch_bucket_size": 5e8,
34
+ "stage3_param_persistence_threshold": 1e6,
35
+ "stage3_max_live_parameters": 1e9,
36
+ "stage3_max_reuse_distance": 1e9,
37
+ "stage3_gather_16bit_weights_on_model_save": true
38
+ },
39
+
40
+ "gradient_accumulation_steps": "auto",
41
+ "gradient_clipping": "auto",
42
+ "train_batch_size": "auto",
43
+ "train_micro_batch_size_per_gpu": "auto",
44
+ "wall_clock_breakdown": false
45
+ }