Views
No views yet
1git clone https://github.com/LUMIA-Group/MemSFT.git
2cd MemSFT
3
4conda create -n memsft-generate python=3.10 pip -y
5conda activate memsft-generate
6python -m pip install -e .
7python -m pip install \
8 "torch>=2.4,<2.7" \
9 "transformers==4.51.3" \
10 "huggingface-hub==0.35.3" \
11 "accelerate>=0.34,<2"1from pathlib import Path
2
3import torch
4from huggingface_hub import snapshot_download
5from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
6
7from memsft.router.adaptive_memdec import AdaptiveMemoryDecoder
8
9device = torch.device("cuda:0")
10base_id = "Qwen/Qwen3-14B"
11memory_id = "Jiarui-Wang/MemSFT-Qwen3-OpenSWI-Memory-8B"
12router_repo = "Jiarui-Wang/MemSFT-Qwen3-Routers"
13router_subdir = "Qwen3-14B-OpenSWI-M8B-Router"
14
15router_root = snapshot_download(
16 repo_id=router_repo,
17 revision="v1.0.0",
18 allow_patterns=[f"{router_subdir}/*"],
19)
20router_path = str(Path(router_root) / router_subdir)
21
22tokenizer = AutoTokenizer.from_pretrained(
23 base_id,
24 revision="40c069824f4251a91eefaf281ebe4c544efd3e18",
25)
26base = AutoModelForCausalLM.from_pretrained(
27 base_id,
28 revision="40c069824f4251a91eefaf281ebe4c544efd3e18",
29 torch_dtype=torch.bfloat16,
30 low_cpu_mem_usage=True,
31).to(device).eval()
32memory = AutoModelForCausalLM.from_pretrained(
33 memory_id,
34 revision="v1.0.0",
35 torch_dtype=torch.bfloat16,
36 low_cpu_mem_usage=True,
37).to(device).eval()
38
39vocab_size = len(tokenizer)
40base.resize_token_embeddings(vocab_size)
41memory.resize_token_embeddings(vocab_size)
42base.requires_grad_(False)
43memory.requires_grad_(False)
44model = AdaptiveMemoryDecoder(
45 base_lm=base,
46 knn_generator=memory,
47 router_path=router_path,
48 router_device=device,
49).eval()
50model.set_tokenizer(tokenizer)1import json
2
3example_path = Path("data/openswi_shallow_1k/shallow/test.jsonl")
4with example_path.open("r", encoding="utf-8") as handle:
5 example = next(
6 record
7 for record in map(json.loads, handle)
8 if record["id_ddm"] == 707
9 )
10
11system_prompt = (
12 "You are a professional geophysical inversion expert, proficient in "
13 "utilizing surface wave dispersion data to infer subsurface S-wave "
14 "velocity structures. Based on the provided surface wave dispersion "
15 "data, perform nonlinear inversion to obtain an S-wave velocity (Vs) "
16 "sequence at specified depth points."
17)
18format_instruction = (
19 'Strict format requirements:\n'
20 '1) Return exactly one valid Python list of floats.\n'
21 '2) The list length must be exactly M (M = "layers in total" in the input prompt).\n'
22 '3) Output must start with "[" and end with "]".\n'
23 '4) Output only one line, no prefix/suffix text, no reasoning, no markdown, no code block.\n'
24 'Final answer format example: [0.3123, 0.4210, ...]'
25)
26prompt = f"{system_prompt}\n\n{example['prompt']}\n{format_instruction}"
27
28messages = [{"role": "user", "content": prompt}]
29prompt_text = tokenizer.apply_chat_template(
30 messages,
31 tokenize=False,
32 add_generation_prompt=True,
33 enable_thinking=False,
34)
35inputs = tokenizer(prompt_text, return_tensors="pt").to(device)
36
37set_seed(42)
38with torch.inference_mode():
39 output_ids = model.generate(
40 **inputs,
41 do_sample=True,
42 temperature=0.6,
43 top_p=0.95,
44 top_k=20,
45 max_new_tokens=8192,
46 eos_token_id=tokenizer.eos_token_id,
47 pad_token_id=tokenizer.eos_token_id,
48 )
49
50answer = tokenizer.decode(
51 output_ids[0, inputs["input_ids"].shape[1]:],
52 skip_special_tokens=True,
53)
54print(answer)[1.3487,1.3487,1.3487,1.3487,1.3487,1.3487,1.3487,1.3487,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,1.8542,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.2210,2.1837]id_ddm=707, the raw MemSFT output contains the required
70 values and has an RMSE of 0.1404. OpenSWI targets a layered S-wave
velocity profile, so identical values across consecutive depth layers are
expected. With the same prompt, seed, and generation settings, Qwen3-14B alone
produces a 96-value list instead of the required 70-value sequence. The
router's mean memory mixing weight is 0.9900, with the memory route receiving
the larger weight on 99.8% of generation steps. The outputs were reproduced in
BF16 on an NVIDIA A800 80GB GPU.| Base model | OpenSWI RMSE ↓ | General ↑ |
|---|---|---|
| Qwen3-8B + MemSFT | 0.47 | 81.13 |
| Qwen3-14B + MemSFT | 0.47 | 83.80 |
| Qwen3-32B + MemSFT | 0.47 | 85.41 |
| Qwen3-235B-A22B + MemSFT | 0.47 | 87.22 |
Qwen/Qwen3-14BJiarui-Wang/MemSFT-Qwen3-OpenSWI-Memory-8BJiarui-Wang/MemSFT-Qwen3-Routers/Qwen3-14B-OpenSWI-M8B-Router.safetensors router checkpoints. Legacy .pt
checkpoints should be loaded only from trusted sources; the MemSFT loader uses
PyTorch's restricted weights_only=True mode for compatibility.1@misc{wang2026memsftmitigatingalignmenttax,
2 title={MemSFT: Mitigating Alignment Tax with an External Parametric Memory},
3 author={Jiarui Wang and Xiang Shi and Jiaqi Cao and Rubin Wei and Xiquan Wang and Hao Sun and Jingzhi Wang and Zhiqi Yang and Qipeng Guo and Bowen Zhou and Zhouhan Lin},
4 year={2026},
5 eprint={2607.25614},
6 archivePrefix={arXiv},
7 primaryClass={cs.LG},
8 url={https://arxiv.org/abs/2607.25614},
9}