xujjia commited on
Commit
ae34de5
·
verified ·
1 Parent(s): 4c85628
qwen2.5-7b_c4_1B/scripts/bits_llm.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Bits-for-LLM encoder (extended): token id -> fixed random n-bit code -> encoder -> hidden.
3
+ Backward compatible: defaults (enc_type='mlp', ffn_depth=2, out_scale=0) reproduce the
4
+ original 2-layer MLP exactly. New encoder-only tuning knobs for the 7B sweep:
5
+ - ffn_depth : number of Linear layers in the MLP (2 = original, 3 = deeper)
6
+ - out_scale : if >0, rescale the final projection so output std ~= out_scale (init-scale match)
7
+ - enc_type='tfenc' : small Transformer encoder over the bit-code (TF-Enc-H4-FFN512 replica)
8
+ """
9
+ import torch, torch.nn as nn
10
+
11
+
12
+ class BitsEmbedding(nn.Module):
13
+ def __init__(self, vocab, hidden, nbits=64, W=256, seed=0, dtype=torch.bfloat16,
14
+ enc_type='mlp', ffn_depth=2, out_scale=0.0):
15
+ super().__init__()
16
+ g = torch.Generator().manual_seed(seed)
17
+ # plain attribute (NOT a buffer) so FSDP never shards it
18
+ self._codes = torch.randint(0, 2, (vocab, nbits), generator=g).to(dtype)
19
+ self.vocab, self.hidden, self.nbits, self.W = vocab, hidden, nbits, W
20
+ self.enc_type, self.ffn_depth = enc_type, ffn_depth
21
+ self.padding_idx = None
22
+ if enc_type == 'tfenc':
23
+ self.tok_bits = 16
24
+ assert nbits % self.tok_bits == 0, "nbits must be divisible by 16 for tfenc"
25
+ self.seq = nbits // self.tok_bits
26
+ self.in_proj = nn.Linear(self.tok_bits, W)
27
+ self.pos = nn.Parameter(torch.zeros(self.seq, W))
28
+ layer = nn.TransformerEncoderLayer(d_model=W, nhead=4, dim_feedforward=512,
29
+ activation='gelu', batch_first=True, norm_first=True)
30
+ self.tfenc = nn.TransformerEncoder(layer, num_layers=2)
31
+ self.out_proj = nn.Linear(W, hidden)
32
+ self.to(dtype)
33
+ else: # mlp with ffn_depth linear layers (>=2)
34
+ layers = [nn.Linear(nbits, W), nn.GELU()]
35
+ for _ in range(max(0, ffn_depth - 2)):
36
+ layers += [nn.Linear(W, W), nn.GELU()]
37
+ layers += [nn.Linear(W, hidden)]
38
+ self.ffn = nn.Sequential(*layers).to(dtype)
39
+ if out_scale and out_scale > 0:
40
+ self._match_out_scale(out_scale)
41
+
42
+ def _final(self):
43
+ return self.out_proj if self.enc_type == 'tfenc' else self.ffn[-1]
44
+
45
+ def _match_out_scale(self, target_std):
46
+ # empirically rescale the final (linear) layer so the OUTPUT embedding std ~= target_std,
47
+ # matching the standard embedding's init scale (~initializer_range). Output is linear in
48
+ # the final layer for both mlp (ffn[-1]) and tfenc (out_proj), so scaling it scales output.
49
+ with torch.no_grad():
50
+ idx = torch.randint(0, self.vocab, (4096,))
51
+ cur = self.forward(idx).float().std().item()
52
+ if cur > 1e-8:
53
+ k = target_std / cur
54
+ last = self._final()
55
+ last.weight.mul_(k)
56
+ if last.bias is not None:
57
+ last.bias.mul_(k)
58
+
59
+ def forward(self, ids):
60
+ if self._codes.device != ids.device:
61
+ self._codes = self._codes.to(ids.device)
62
+ codes = self._codes[ids]
63
+ if self.enc_type == 'tfenc':
64
+ *lead, n = codes.shape
65
+ x = codes.reshape(-1, self.seq, self.tok_bits)
66
+ x = self.in_proj(x) + self.pos
67
+ x = self.tfenc(x).mean(dim=1)
68
+ return self.out_proj(x).reshape(*lead, self.hidden)
69
+ return self.ffn(codes)
70
+
71
+ def n_params(self):
72
+ return sum(p.numel() for p in self.parameters())
73
+
74
+
75
+ def swap_in_bits(model, nbits, W, seed, enc_type='mlp', ffn_depth=2, out_scale=0.0):
76
+ cfg = model.config
77
+ if getattr(cfg, "tie_word_embeddings", False):
78
+ out = model.get_output_embeddings()
79
+ if out is not None:
80
+ new = nn.Linear(out.weight.shape[1], out.weight.shape[0], bias=False)
81
+ new.weight = nn.Parameter(out.weight.detach().clone())
82
+ model.set_output_embeddings(new); cfg.tie_word_embeddings = False
83
+ dtype = next(model.parameters()).dtype
84
+ bits = BitsEmbedding(cfg.vocab_size, cfg.hidden_size, nbits, W, seed, dtype,
85
+ enc_type=enc_type, ffn_depth=ffn_depth, out_scale=out_scale)
86
+ dev = model.get_input_embeddings().weight.device
87
+ model.set_input_embeddings(bits.to(dev))
88
+ orig = cfg.vocab_size * cfg.hidden_size
89
+ print(f"[Bits enc={enc_type} depth={ffn_depth} W={W} nbits={nbits} scale={out_scale}] "
90
+ f"table {orig/1e9:.3f}B -> encoder {bits.n_params()/1e6:.2f}M "
91
+ f"= {orig/max(bits.n_params(),1):.0f}x compression", flush=True)
92
+ return bits
93
+
94
+
95
+ def get_dataset(tokenizer, max_len, n_tokens, split_stream):
96
+ from datasets import load_dataset
97
+ ds = load_dataset("allenai/c4", "en", split=split_stream, streaming=True)
98
+ def gen():
99
+ buf = []
100
+ while True:
101
+ for ex in ds:
102
+ ids = tokenizer(ex["text"], add_special_tokens=False)["input_ids"]
103
+ buf.extend(ids + [tokenizer.eos_token_id])
104
+ while len(buf) >= max_len:
105
+ yield {"input_ids": buf[:max_len], "labels": buf[:max_len]}
106
+ buf = buf[max_len:]
107
+ from datasets import IterableDataset
108
+ return IterableDataset.from_generator(gen)
qwen2.5-7b_c4_1B/scripts/bits_lm_traj.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """EXACT original from-scratch recipe (bits_lm_scratch.py) + intermediate checkpoints every 100M
3
+ tokens, so we get the full 100M->500M trajectory for both arms. Training dynamics are identical to
4
+ the original (only checkpoint saving is added).
5
+ Camera-ready additions (2026-11-05): transformers 5 warmup_steps (= ceil(0.02*max_steps), what
6
+ warmup_ratio=0.02 computed before), multi-GPU token accounting via WORLD_SIZE, optional FSDP
7
+ sharding (same optimizer step, states sharded across ranks), keep all checkpoints."""
8
+ import argparse, sys, math, os, torch
9
+ sys.path.insert(0, '/root')
10
+ from bits_llm import swap_in_bits, get_dataset
11
+ from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM, TrainingArguments, Trainer, TrainerCallback
12
+ import subprocess
13
+
14
+
15
+ class Bf16Save(TrainerCallback):
16
+ """Disk only: after each save, rewrite the fp32 safetensors of that checkpoint to bf16 in a detached
17
+ process (30G -> 15G per 7B checkpoint). Training dynamics untouched."""
18
+ def on_save(self, args, state, control, **kw):
19
+ if state.is_world_process_zero:
20
+ d = os.path.join(args.output_dir, f'checkpoint-{state.global_step}')
21
+ subprocess.Popen([sys.executable, '/root/to_bf16.py', d], stdout=open(d + '/bf16.log', 'w'), stderr=subprocess.STDOUT)
22
+
23
+
24
+ def main():
25
+ ap = argparse.ArgumentParser()
26
+ ap.add_argument('--arm', required=True, choices=['bits', 'standard'])
27
+ ap.add_argument('--model', default='Qwen/Qwen2.5-7B')
28
+ ap.add_argument('--tokens', type=int, default=500_000_000)
29
+ ap.add_argument('--max_len', type=int, default=512)
30
+ ap.add_argument('--bs', type=int, default=8)
31
+ ap.add_argument('--grad_accum', type=int, default=8)
32
+ ap.add_argument('--lr', type=float, default=3e-4)
33
+ ap.add_argument('--nbits', type=int, default=256)
34
+ ap.add_argument('--W', type=int, default=512)
35
+ ap.add_argument('--seed', type=int, default=42)
36
+ ap.add_argument('--out', required=True)
37
+ ap.add_argument('--fsdp', action='store_true')
38
+ ap.add_argument('--save_tokens', type=int, default=100_000_000)
39
+ a = ap.parse_args()
40
+ torch.manual_seed(a.seed)
41
+ tok = AutoTokenizer.from_pretrained(a.model)
42
+ if tok.pad_token is None:
43
+ tok.pad_token = tok.eos_token
44
+ cfg = AutoConfig.from_pretrained(a.model)
45
+ model = AutoModelForCausalLM.from_config(cfg, torch_dtype=torch.bfloat16, attn_implementation='sdpa')
46
+ if not a.fsdp:
47
+ model = model.cuda()
48
+ if a.arm == 'bits':
49
+ swap_in_bits(model, a.nbits, a.W, a.seed)
50
+ if not a.fsdp:
51
+ model = model.cuda()
52
+ nparam = sum(p.numel() for p in model.parameters())
53
+ emb = sum(p.numel() for p in model.get_input_embeddings().parameters())
54
+ print(f"[{a.arm} s{a.seed}] total={nparam/1e6:.0f}M embed={emb/1e6:.2f}M", flush=True)
55
+ train_ds = get_dataset(tok, a.max_len, a.tokens, 'train')
56
+ world = int(os.environ.get('WORLD_SIZE', 1))
57
+ tps = a.max_len * a.bs * a.grad_accum * world # tokens per optimizer step across all ranks
58
+ max_steps = max(1, a.tokens // tps)
59
+ save_every = max(1, a.save_tokens // tps) # checkpoint every 100M tokens
60
+ extra = {}
61
+ if a.fsdp:
62
+ extra = dict(fsdp='full_shard auto_wrap',
63
+ fsdp_config={'transformer_layer_cls_to_wrap': ['Qwen2DecoderLayer']})
64
+ targs = TrainingArguments(
65
+ output_dir=a.out, per_device_train_batch_size=a.bs, gradient_accumulation_steps=a.grad_accum,
66
+ learning_rate=a.lr, max_steps=max_steps, logging_steps=10, bf16=True,
67
+ save_strategy='steps', save_steps=save_every, save_total_limit=None, save_only_model=True,
68
+ optim='adamw_torch', gradient_checkpointing=True, report_to=['tensorboard'], dataloader_num_workers=2,
69
+ warmup_steps=math.ceil(0.02 * max_steps), lr_scheduler_type='cosine', max_grad_norm=1.0, seed=a.seed,
70
+ **extra)
71
+ def collate(b):
72
+ return {'input_ids': torch.tensor([x['input_ids'] for x in b]),
73
+ 'labels': torch.tensor([x['labels'] for x in b])}
74
+ tr = Trainer(model=model, args=targs, train_dataset=train_ds, data_collator=collate)
75
+ tr.add_callback(Bf16Save())
76
+ print(f"[{a.arm} s{a.seed}] world={world} tokens/step={tps} max_steps={max_steps} save_every={save_every}", flush=True)
77
+ tr.train()
78
+ tr.save_model(a.out + '/final')
79
+ final = tr.state.log_history[-1].get('loss', float('nan')) if tr.state.log_history else float('nan')
80
+ print(f"[{a.arm} s{a.seed}] max_steps={max_steps} save_every={save_every} DONE final_loss~={final}", flush=True)
81
+
82
+
83
+ if __name__ == '__main__':
84
+ main()
qwen2.5-7b_c4_1B/scripts/eval_ppl.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Held-out perplexity eval for Bits vs Standard checkpoints (supports encoder variants).
3
+ Same held-out set, same metric exp(mean per-token CE). For Bits variants, pass the SAME
4
+ --enc_type/--ffn_depth/--W/--nbits used at training so the module structure matches for loading."""
5
+ import argparse, glob, os, math, sys, torch
6
+ os.environ['HF_HUB_OFFLINE'] = '1'
7
+ os.environ['TRANSFORMERS_OFFLINE'] = '1'
8
+ import torch.nn.functional as F
9
+ sys.path.insert(0, '/root')
10
+ from transformers import AutoConfig, AutoModelForCausalLM
11
+ from safetensors.torch import load_file
12
+
13
+
14
+ def build(a):
15
+ cfg = AutoConfig.from_pretrained(a.ckpt)
16
+ if a.arm == 'standard':
17
+ return AutoModelForCausalLM.from_pretrained(
18
+ a.ckpt, torch_dtype=torch.bfloat16, attn_implementation='sdpa').cuda().eval()
19
+ from bits_llm import swap_in_bits
20
+ model = AutoModelForCausalLM.from_config(cfg, torch_dtype=torch.bfloat16, attn_implementation='sdpa')
21
+ swap_in_bits(model, a.nbits, a.W, a.seed, enc_type=a.enc_type, ffn_depth=a.ffn_depth, out_scale=0.0)
22
+ sd = {}
23
+ for f in glob.glob(os.path.join(a.ckpt, '*.safetensors')):
24
+ sd.update(load_file(f))
25
+ missing, unexpected = model.load_state_dict(sd, strict=False)
26
+ enc_keys = [k for k in sd if 'embed_tokens' in k or '.ffn.' in k or 'in_proj' in k or 'out_proj' in k or 'tfenc' in k]
27
+ print(f"[load] tensors={len(sd)} enc_keys={len(enc_keys)} missing={len(missing)} unexpected={len(unexpected)}", flush=True)
28
+ if len(enc_keys) == 0:
29
+ print("[load][FATAL] no encoder weights in checkpoint", flush=True); sys.exit(3)
30
+ return model.cuda().eval()
31
+
32
+
33
+ @torch.no_grad()
34
+ def evaluate(model, seqs, bs):
35
+ tot_loss, tot_tok = 0.0, 0
36
+ for i in range(0, len(seqs), bs):
37
+ batch = torch.tensor(seqs[i:i+bs]).cuda()
38
+ logits = model(input_ids=batch).logits[:, :-1, :].float()
39
+ labels = batch[:, 1:]
40
+ loss = F.cross_entropy(logits.reshape(-1, logits.size(-1)), labels.reshape(-1), reduction='sum')
41
+ tot_loss += loss.item(); tot_tok += labels.numel()
42
+ ml = tot_loss / tot_tok
43
+ return ml, math.exp(ml)
44
+
45
+
46
+ def main():
47
+ ap = argparse.ArgumentParser()
48
+ ap.add_argument('--arm', required=True, choices=['bits', 'standard'])
49
+ ap.add_argument('--ckpt', required=True)
50
+ ap.add_argument('--data', default='/root/heldout_c4val_4000x512.pt')
51
+ ap.add_argument('--nbits', type=int, default=256)
52
+ ap.add_argument('--W', type=int, default=512)
53
+ ap.add_argument('--enc_type', default='mlp', choices=['mlp', 'tfenc'])
54
+ ap.add_argument('--ffn_depth', type=int, default=2)
55
+ ap.add_argument('--seed', type=int, default=42)
56
+ ap.add_argument('--n_seq', type=int, default=4000)
57
+ ap.add_argument('--bs', type=int, default=8)
58
+ a = ap.parse_args()
59
+ model = build(a)
60
+ seqs = torch.load(a.data)[:a.n_seq]
61
+ ml, ppl = evaluate(model, seqs, a.bs)
62
+ print(f"RESULT arm={a.arm} ckpt={a.ckpt} enc={a.enc_type} depth={a.ffn_depth} W={a.W} nbits={a.nbits} "
63
+ f"tokens={len(seqs)*len(seqs[0])} eval_loss={ml:.4f} ppl={ppl:.2f}", flush=True)
64
+
65
+
66
+ if __name__ == '__main__':
67
+ main()
qwen2.5-7b_c4_1B/scripts/eval_seed.sh ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # usage: eval_seed.sh SEED ARM GPU -> appends RESULT lines to /root/traj/s3_1B/eval_s${SEED}_${ARM}.log
3
+ seed=$1; arm=$2; gpu=$3
4
+ source /venv/main/bin/activate; cd /root
5
+ dir=/root/traj/s3_1B/s${seed}_$([ $arm = bits ] && echo bits || echo std)
6
+ out=/root/traj/s3_1B/eval_s${seed}_${arm}.log
7
+ for c in $(ls -d $dir/checkpoint-* | sort -t- -k2 -n) $dir/final; do
8
+ grep -q "ckpt=$c " $out 2>/dev/null && continue
9
+ CUDA_VISIBLE_DEVICES=$gpu python eval_ppl.py --arm $arm --ckpt $c --seed $seed 2>&1 | grep -aE "RESULT|FATAL|Traceback|Error" >> $out
10
+ done
11
+ echo "EVAL_DONE seed=$seed arm=$arm" >> $out
qwen2.5-7b_c4_1B/scripts/heldout_c4val_4000x512.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1484c78c496936bc7264647e1a253237791ba318953a71f0a4b62425228714cd
3
+ size 6015111
qwen2.5-7b_c4_1B/scripts/hf_arm.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, sys, time
2
+ os.environ["HF_HUB_ENABLE_HF_TRANSFER"]="1"
3
+ from huggingface_hub import HfApi
4
+ arm=sys.argv[1]; tok=sys.stdin.readline().strip()
5
+ api=HfApi(token=tok); repo="bit2vec/bitsseal"; base="/root/traj/s3_1B"
6
+ for attempt in range(8):
7
+ try:
8
+ api.upload_folder(folder_path=f"{base}/{arm}", repo_id=repo, path_in_repo=f"qwen2.5-7b_c4_1B/{arm}",
9
+ allow_patterns=["checkpoint-*/*.safetensors","checkpoint-*/*.json","checkpoint-*/bf16.log"], commit_message=f"{arm}: all checkpoints")
10
+ print("ARM_UPLOADED", arm, time.strftime("%H:%M:%S"), flush=True); break
11
+ except Exception as e:
12
+ print("RETRY", arm, attempt, repr(e)[:300], flush=True); time.sleep(300)
13
+ else:
14
+ print("GAVEUP", arm, flush=True)
15
+ if arm=="s42_bits":
16
+ api.upload_folder(folder_path=base, repo_id=repo, path_in_repo="qwen2.5-7b_c4_1B/logs", allow_patterns=["*.log"], ignore_patterns=["**/checkpoint-*/**","**/runs/**"], commit_message="logs")
17
+ api.upload_folder(folder_path="/root", repo_id=repo, path_in_repo="qwen2.5-7b_c4_1B/scripts", allow_patterns=["bits_llm.py","bits_lm_traj.py","eval_ppl.py","eval_seed.sh","run_3seeds_1B.sh","to_bf16.py","hf_arm.py","heldout_c4val_4000x512.pt"], commit_message="scripts")
18
+ print("LOGS_SCRIPTS_DONE", flush=True)
qwen2.5-7b_c4_1B/scripts/run_3seeds_1B.sh ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # 3 seed pairs at 1B tokens, both arms of a seed at once on 4 GPUs each, seeds back to back.
3
+ cd /root
4
+ export FSDP_MODE="full_shard auto_wrap"
5
+ OUT=/root/traj/s3_1B; mkdir -p $OUT
6
+ for seed in 43 44 42; do
7
+ echo "[$(date -u +%FT%TZ)] START seed $seed" >> $OUT/driver.log
8
+ CUDA_VISIBLE_DEVICES=0,1,2,3 torchrun --nproc_per_node 4 --master_port 29501 bits_lm_traj.py --arm bits --bs 16 --grad_accum 1 --tokens 1000000000 --seed $seed --fsdp --out $OUT/s${seed}_bits > $OUT/s${seed}_bits.log 2>&1 &
9
+ P1=$!
10
+ CUDA_VISIBLE_DEVICES=4,5,6,7 torchrun --nproc_per_node 4 --master_port 29502 bits_lm_traj.py --arm standard --bs 16 --grad_accum 1 --tokens 1000000000 --seed $seed --fsdp --out $OUT/s${seed}_std > $OUT/s${seed}_std.log 2>&1 &
11
+ P2=$!
12
+ wait $P1; R1=$?; wait $P2; R2=$?
13
+ echo "[$(date -u +%FT%TZ)] END seed $seed bits_rc=$R1 std_rc=$R2" >> $OUT/driver.log
14
+ done
15
+ echo "[$(date -u +%FT%TZ)] ALL_SEEDS_DONE" >> $OUT/driver.log
qwen2.5-7b_c4_1B/scripts/to_bf16.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys, os, glob, torch
2
+ from safetensors.torch import load_file, save_file
3
+ d = sys.argv[1]
4
+ for p in sorted(glob.glob(os.path.join(d, '*.safetensors'))):
5
+ sd = load_file(p)
6
+ n = sum(v.numel() for v in sd.values())
7
+ sd = {k: (v.to(torch.bfloat16) if v.is_floating_point() else v) for k, v in sd.items()}
8
+ save_file(sd, p + '.tmp', metadata={'format': 'pt'})
9
+ os.replace(p + '.tmp', p)
10
+ print('bf16', p, n, flush=True)
11
+ print('DONE', flush=True)