#!/usr/bin/env python3 """Convert a tuned vanilla Qwen3 checkpoint into a quality-safe AHA hot start. All vanilla weights are preserved. Dynamic gate weights start at zero and a trainable bias initializes every gate to the requested full-attention probability, so hard routing is initially exactly vanilla full attention. """ from __future__ import annotations import argparse import json import math import sys from pathlib import Path import torch from torch import nn from transformers import AutoTokenizer ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from modeling_aha_qwen3 import AHAQwen3Config, AHAQwen3ForCausalLM def add_zero_bias(linear: nn.Linear) -> nn.Linear: new = nn.Linear( linear.in_features, linear.out_features, bias=True, device=linear.weight.device, dtype=linear.weight.dtype, ) with torch.no_grad(): new.weight.copy_(linear.weight) new.bias.zero_() return new def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--vanilla-path", required=True) parser.add_argument("--output-path", required=True) parser.add_argument("--window-size", type=int, default=128) parser.add_argument("--gate-init-full-prob", type=float, default=0.90) parser.add_argument( "--local-kind", choices=("sliding_window", "sink_recent"), default="sliding_window" ) parser.add_argument( "--router-granularity", choices=("token", "token_kv_head"), default="token_kv_head", help="Native dynamic gate shape. token is one shared gate per layer/token.", ) args = parser.parse_args() if not 0.5 < args.gate_init_full_prob < 1.0: raise ValueError("--gate-init-full-prob must be strictly between 0.5 and 1") output = Path(args.output_path) output.mkdir(parents=True, exist_ok=True) AHAQwen3Config.register_for_auto_class() AHAQwen3ForCausalLM.register_for_auto_class("AutoModelForCausalLM") model = AHAQwen3ForCausalLM.from_pretrained_qwen3( args.vanilla_path, aha_window_size=args.window_size, aha_local_kind=args.local_kind, aha_router_granularity=args.router_granularity, aha_mode="dynamic", aha_gate_target=0.70, aha_reg_weight=-1.0, torch_dtype=torch.bfloat16, attn_implementation="sdpa", ) # Qwen3 normally has bias-free projections. A gate bias gives a stable, # token-independent full-attention hot start while zero biases on q/k/v/o # keep the original attention computation bit-for-bit unchanged. if not model.config.attention_bias: for layer in model.model.layers: attn = layer.self_attn attn.q_proj = add_zero_bias(attn.q_proj) attn.k_proj = add_zero_bias(attn.k_proj) attn.v_proj = add_zero_bias(attn.v_proj) attn.o_proj = add_zero_bias(attn.o_proj) model.config.attention_bias = True q_rows = model.config.num_attention_heads * model.config.head_dim gate_logit = math.log(args.gate_init_full_prob / (1.0 - args.gate_init_full_prob)) with torch.no_grad(): for layer in model.model.layers: q_proj = layer.self_attn.q_proj q_proj.weight[q_rows:].zero_() q_proj.bias[q_rows:].fill_(gate_logit) # Keep a distinct LM head so the custom checkpoint has an explicit, # self-contained state dict under safetensors. model.config.tie_word_embeddings = False if model.lm_head.weight.data_ptr() == model.model.embed_tokens.weight.data_ptr(): model.lm_head.weight = nn.Parameter(model.lm_head.weight.detach().clone()) model.config.aha_hotstart_source = str(Path(args.vanilla_path).resolve()) model.config.aha_gate_init_full_prob = args.gate_init_full_prob tokenizer = AutoTokenizer.from_pretrained(args.vanilla_path, trust_remote_code=True) model.save_pretrained(output, safe_serialization=True) tokenizer.save_pretrained(output) gate_rows = model.model.layers[0].self_attn.aha_router_outputs gate_parameters = len(model.model.layers) * gate_rows * ( model.config.hidden_size + 1 ) (output / "aha_hotstart_manifest.json").write_text( json.dumps( { "source_checkpoint": str(Path(args.vanilla_path).resolve()), "router_granularity": args.router_granularity, "native_gate_rows_per_layer": gate_rows, "effective_gate_parameters": gate_parameters, "gate_init_full_probability": args.gate_init_full_prob, "gate_weight_init": "zeros", "gate_bias_logit": gate_logit, "local_attention": { "kind": args.local_kind, "sink_size": model.config.duo_sink_size, "recent_size": model.config.duo_recent_size, }, }, indent=2, ) + "\n" ) print( f"saved={output} init_full_prob={args.gate_init_full_prob:.4f} " f"gate_logit={gate_logit:.6f} hard_sparsity=0.0 " f"router_granularity={args.router_granularity} gate_params={gate_parameters:,}" ) if __name__ == "__main__": main()