| """Experiment: reload the pre-quantized 3.32 GB checkpoint WITHOUT the bf16 base |
| load — build the RBM skeleton from config (random init), install Linear4bit, then |
| load_state_dict the packed NF4 weights. Verify forward reproduces the ramp. |
| |
| Prereq: run build_experiment.py first (writes /tmp/robometer-nf4-ckpt/model.safetensors). |
| Run: /tmp/robometer-env/bin/python rskills/robometer-4b/_vendor/reload_experiment.py |
| """ |
|
|
| from __future__ import annotations |
|
|
| import os |
| import pathlib |
| import resource |
| import time |
| from dataclasses import fields |
|
|
| import numpy as np |
| import yaml |
|
|
| |
| os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") |
|
|
| import torch |
|
|
| torch.backends.cudnn.allow_tf32 = False |
| torch.use_deterministic_algorithms(True, warn_only=True) |
| torch.backends.cuda.enable_flash_sdp(False) |
| torch.backends.cuda.enable_mem_efficient_sdp(False) |
| torch.backends.cuda.enable_math_sdp(True) |
|
|
| CKPT = pathlib.Path("/tmp/robometer-nf4-ckpt") |
| MIN_PARAMS = 4_000_000 |
|
|
|
|
| def _rss_gb() -> float: |
| return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1e6 |
|
|
|
|
| _BNB_META_SUFFIXES = ( |
| ".absmax", |
| ".quant_map", |
| ".nested_absmax", |
| ".nested_quant_map", |
| ".quant_state.bitsandbytes__nf4", |
| ".quant_state.bitsandbytes__fp4", |
| ) |
|
|
|
|
| def _install_prequantized(policy, state, device): |
| """Inlined openral_sim._quantization.install_prequantized_linears.""" |
| import bitsandbytes as bnb |
|
|
| consumed: set[str] = set() |
| count = 0 |
| for prefix, module in policy.named_modules(): |
| if not isinstance(module, bnb.nn.Linear4bit): |
| continue |
| wkey = f"{prefix}.weight" |
| if wkey not in state: |
| continue |
| stats = {} |
| for suf in _BNB_META_SUFFIXES: |
| full = f"{wkey}{suf}" |
| if full in state: |
| stats[suf.lstrip(".")] = state[full] |
| consumed.add(full) |
| consumed.add(wkey) |
| module.weight = bnb.nn.Params4bit.from_prequantized( |
| data=state[wkey], quantized_stats=stats, requires_grad=False, device=device |
| ) |
| bkey = f"{prefix}.bias" |
| if module.bias is not None and bkey in state: |
| module.bias = torch.nn.Parameter(state[bkey].to(device), requires_grad=False) |
| consumed.add(bkey) |
| count += 1 |
| return count, consumed |
|
|
|
|
| def _quantize_structure(root, compute_dtype): |
| """Replace large Linears with Linear4bit (empty packed params) — structure only.""" |
| import bitsandbytes as bnb |
|
|
| n = 0 |
|
|
| def _replace(m): |
| nonlocal n |
| for name, child in list(m.named_children()): |
| if isinstance(child, torch.nn.Linear) and child.weight.numel() >= MIN_PARAMS: |
| new = bnb.nn.Linear4bit( |
| child.in_features, |
| child.out_features, |
| bias=child.bias is not None, |
| compute_dtype=compute_dtype, |
| quant_type="nf4", |
| ) |
| setattr(m, name, new) |
| n += 1 |
| else: |
| _replace(child) |
|
|
| _replace(root) |
| return n |
|
|
|
|
| def main() -> int: |
| from huggingface_hub import hf_hub_download |
| from robometer.configs.experiment_configs import ExperimentConfig |
| from robometer.models.rbm import RBM |
| from safetensors.torch import load_file |
| from transformers import AutoConfig, AutoProcessor, AutoTokenizer |
|
|
| base_id = "Qwen/Qwen3-VL-4B-Instruct" |
| |
| cfg_yaml = hf_hub_download("robometer/Robometer-4B", "config.yaml") |
| raw = yaml.safe_load(open(cfg_yaml)) |
| valid = {f.name for f in fields(ExperimentConfig)} |
| exp_config = ExperimentConfig(**{k: v for k, v in raw.items() if k in valid}) |
|
|
| |
| |
| config = AutoConfig.from_pretrained(str(CKPT)) |
| processor = AutoProcessor.from_pretrained(str(CKPT)) |
| tokenizer = AutoTokenizer.from_pretrained(str(CKPT)) |
| |
| |
| |
| |
| for c in (config, getattr(config, "text_config", None), getattr(config, "vision_config", None)): |
| if c is not None: |
| c._attn_implementation = "sdpa" |
|
|
| print( |
| f"[reload] tf32 matmul={torch.backends.cuda.matmul.allow_tf32} " |
| f"cudnn.tf32={torch.backends.cudnn.allow_tf32} " |
| f"fp32_precision(matmul)={torch.get_float32_matmul_precision()}", |
| flush=True, |
| ) |
|
|
| t0 = time.monotonic() |
| print("[reload] building RBM skeleton on META (instant, no weights) ...", flush=True) |
| with torch.device("meta"): |
| model = RBM( |
| config, |
| processor, |
| tokenizer, |
| base_model=None, |
| base_model_id=base_id, |
| model_config=exp_config.model, |
| ) |
| n = _quantize_structure(model, compute_dtype=torch.bfloat16) |
| print( |
| f"[reload] meta skeleton + {n} Linear4bit shells in " |
| f"{time.monotonic() - t0:.1f}s; peak RSS {_rss_gb():.1f} GB", |
| flush=True, |
| ) |
|
|
| t1 = time.monotonic() |
| state = load_file(str(CKPT / "model.safetensors"), device="cuda") |
| |
| n_q, consumed = _install_prequantized(model, state, device="cuda") |
| |
| leftover = {k: v for k, v in state.items() if k not in consumed} |
| missing, unexpected = model.load_state_dict(leftover, strict=False, assign=True) |
| |
| |
| |
| |
| loaded_bufs, recomputed_meta = 0, 0 |
| for bname, buf in list(model.named_buffers()): |
| if not buf.is_meta: |
| continue |
| parent = model.get_submodule(bname.rsplit(".", 1)[0]) if "." in bname else model |
| leaf = bname.rsplit(".", 1)[-1] |
| if bname in state: |
| parent.register_buffer(leaf, state[bname].to("cuda"), persistent=False) |
| loaded_bufs += 1 |
| elif hasattr(parent, "rope_init_fn") and hasattr(parent, "config"): |
| inv_freq, scaling = parent.rope_init_fn(parent.config, "cuda") |
| parent.register_buffer(leaf, inv_freq, persistent=False) |
| if hasattr(parent, "attention_scaling"): |
| parent.attention_scaling = scaling |
| recomputed_meta += 1 |
| else: |
| dim = 2 * buf.shape[0] |
| inv_freq = 1.0 / ( |
| 10000.0 ** (torch.arange(0, dim, 2, dtype=torch.float, device="cuda") / dim) |
| ) |
| parent.register_buffer(leaf, inv_freq, persistent=False) |
| recomputed_meta += 1 |
| |
| for _nm, mod in model.named_modules(): |
| ifb = getattr(mod, "inv_freq", None) |
| if ifb is None: |
| continue |
| if hasattr(mod, "original_inv_freq"): |
| mod.original_inv_freq = ifb |
| if ( |
| hasattr(mod, "rope_init_fn") |
| and hasattr(mod, "config") |
| and getattr(mod, "attention_scaling", None) is None |
| ): |
| _, scaling = mod.rope_init_fn(mod.config, "cuda") |
| mod.attention_scaling = scaling |
| print( |
| f"[reload] rotary buffers loaded-from-ckpt={loaded_bufs} " |
| f"recomputed-from-meta(expect 0)={recomputed_meta}" |
| ) |
|
|
| |
| still_meta = [n for n, p in model.named_parameters() if p.is_meta] |
| still_meta_buf = [n for n, b in model.named_buffers() if b.is_meta] |
| print(f"[reload] meta params left: {still_meta[:6]}") |
| print(f"[reload] meta buffers left after rotary fix: {still_meta_buf}") |
| |
| vw = dict(model.named_parameters()).get("model.visual.blocks.0.mlp.linear_fc1.weight") |
| print( |
| f"[reload] vision fc1 weight is_meta={vw.is_meta if vw is not None else 'absent'} " |
| f"dtype={vw.dtype if vw is not None else '-'}" |
| ) |
| torch.cuda.synchronize() |
| print( |
| f"[reload] install_prequantized({n_q}) + load_state_dict in {time.monotonic() - t1:.1f}s; " |
| f"{torch.cuda.memory_allocated() / 1e9:.2f} GB VRAM; " |
| f"missing={len(missing)} unexpected={len(unexpected)} " |
| f"meta_params_left={len(still_meta)} meta_bufs_left={len(still_meta_buf)}", |
| flush=True, |
| ) |
| if missing: |
| print(f"[reload] sample missing keys: {missing[:5]}") |
| if unexpected: |
| print(f"[reload] sample unexpected keys: {unexpected[:5]}") |
|
|
| |
| model.eval() |
| import decord |
| from robometer.data.dataset_types import ProgressSample, Trajectory |
| from robometer.evals.eval_server import compute_batch_outputs |
| from robometer.utils.setup_utils import setup_batch_collator |
|
|
| vr = decord.VideoReader("/tmp/robometer_example.mp4") |
| step = max(1, int(round(vr.get_avg_fps() / 3.0))) |
| idx = list(range(0, len(vr), step))[:10] |
| frames = vr.get_batch(idx).asnumpy().astype(np.uint8) |
| collator = setup_batch_collator(processor, tokenizer, exp_config, is_eval=True) |
| traj = Trajectory( |
| frames=frames, |
| frames_shape=tuple(frames.shape), |
| task="Pick up the object and place it in the container", |
| id="0", |
| metadata={"subsequence_length": int(frames.shape[0])}, |
| video_embeddings=None, |
| ) |
| batch = collator([ProgressSample(trajectory=traj, sample_type="progress")]) |
| inp = batch["progress_inputs"] |
| for k, v in inp.items(): |
| if hasattr(v, "to"): |
| inp[k] = v.to("cuda") |
| with torch.no_grad(): |
| res = compute_batch_outputs( |
| model, tokenizer, inp, sample_type="progress", is_discrete_mode=True, num_bins=100 |
| ) |
| prog = np.asarray(res["progress_pred"][0], dtype=np.float32) |
| print( |
| f"[reload] progress series (meta+prequantized path): {[round(float(x), 4) for x in prog]}" |
| ) |
| print( |
| "[reload] COMPARE to the build_experiment REFERENCE series — they must " |
| "match element-wise to ~1e-3 for the prequantized load to be trusted.", |
| flush=True, |
| ) |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(main()) |
|
|