495 lines
19 KiB
Python
495 lines
19 KiB
Python
|
|
"""Duo-style hidden-state distillation for AHA dynamic gates.
|
||
|
|
|
||
|
|
This is the clean static-to-dynamic continuation experiment:
|
||
|
|
|
||
|
|
loss = MSE(h_full, h_dynamic) on labelled tokens + reg_weight * mean(gate_soft)
|
||
|
|
|
||
|
|
where h_full is produced by the same frozen checkpoint with every dynamic gate
|
||
|
|
forced to 1.0, and h_dynamic uses the learned per-token gate. For
|
||
|
|
``aha_mode="duo_dynamic"``, "forced to 1.0" means "reproduce the locked static
|
||
|
|
Duo full-head mask"; Duo streaming heads remain streaming. Backbone weights
|
||
|
|
stay frozen; only q_proj gate rows are trainable.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import random
|
||
|
|
import shutil
|
||
|
|
import sys
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
import torch
|
||
|
|
from torch.utils.data import DataLoader
|
||
|
|
from transformers import AutoTokenizer
|
||
|
|
|
||
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
if HERE not in sys.path:
|
||
|
|
sys.path.insert(0, HERE)
|
||
|
|
|
||
|
|
from duo_train import AmDistilledDataset, LongBenchLiteAnswerDataset, collate # noqa: E402
|
||
|
|
from modeling_aha_qwen3 import ( # noqa: E402
|
||
|
|
AHA_ROUTER_GRANULARITY,
|
||
|
|
AHAQwen3Config,
|
||
|
|
AHAQwen3ForCausalLM,
|
||
|
|
aha_router_output_size,
|
||
|
|
)
|
||
|
|
from router_training_utils import configure_gate_only # noqa: E402
|
||
|
|
|
||
|
|
|
||
|
|
def _set_force_gate(model, value):
|
||
|
|
prev = getattr(model.config, "aha_force_gate_value", None)
|
||
|
|
model.config.aha_force_gate_value = value
|
||
|
|
return prev
|
||
|
|
|
||
|
|
|
||
|
|
def _restore_force_gate(model, value):
|
||
|
|
model.config.aha_force_gate_value = value
|
||
|
|
|
||
|
|
|
||
|
|
def _gate_tensors(out):
|
||
|
|
return [g.float() for g in out.all_gate_soft]
|
||
|
|
|
||
|
|
|
||
|
|
def gate_stats_from_output(out) -> dict:
|
||
|
|
with torch.no_grad():
|
||
|
|
soft = torch.cat([g.reshape(-1) for g in _gate_tensors(out)])
|
||
|
|
hard = torch.cat([g.float().reshape(-1) for g in out.all_gate_hard])
|
||
|
|
return {
|
||
|
|
"gate_soft_mean": soft.mean().item(),
|
||
|
|
"gate_soft_std": soft.std().item(),
|
||
|
|
"gate_hard_mean": hard.mean().item(),
|
||
|
|
"gate_min": soft.min().item(),
|
||
|
|
"gate_max": soft.max().item(),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def gate_param_stats(model) -> dict:
|
||
|
|
weights = []
|
||
|
|
biases = []
|
||
|
|
num_heads = model.config.num_attention_heads
|
||
|
|
head_dim = getattr(model.config, "head_dim", model.config.hidden_size // num_heads)
|
||
|
|
q_rows = num_heads * head_dim
|
||
|
|
with torch.no_grad():
|
||
|
|
for layer in model.model.layers:
|
||
|
|
q_proj = layer.self_attn.q_proj
|
||
|
|
weights.append(q_proj.weight[q_rows:].detach().float().reshape(-1).cpu())
|
||
|
|
if q_proj.bias is not None:
|
||
|
|
biases.append(q_proj.bias[q_rows:].detach().float().cpu())
|
||
|
|
w = torch.cat(weights)
|
||
|
|
stats = {
|
||
|
|
"gate_weight_l2": w.norm().item(),
|
||
|
|
"gate_weight_abs_mean": w.abs().mean().item(),
|
||
|
|
}
|
||
|
|
if biases:
|
||
|
|
b = torch.cat(biases)
|
||
|
|
alpha = torch.sigmoid(b)
|
||
|
|
stats.update({
|
||
|
|
"gate_bias_mean": b.mean().item(),
|
||
|
|
"gate_bias_std": b.std().item(),
|
||
|
|
"gate_bias_alpha_mean": alpha.mean().item(),
|
||
|
|
"gate_bias_alpha_gt05": (alpha > 0.5).float().mean().item(),
|
||
|
|
})
|
||
|
|
if getattr(model.config, "aha_mode", "dynamic") == "duo_dynamic":
|
||
|
|
masks = []
|
||
|
|
with torch.no_grad():
|
||
|
|
for layer in model.model.layers:
|
||
|
|
alpha_static = layer.self_attn.full_attention_heads.detach().float()
|
||
|
|
masks.append((alpha_static > 0.5).reshape(-1).cpu())
|
||
|
|
m = torch.cat(masks).float()
|
||
|
|
stats.update({
|
||
|
|
"duo_static_full_frac": m.mean().item(),
|
||
|
|
"duo_static_streaming_frac": (1.0 - m).mean().item(),
|
||
|
|
})
|
||
|
|
return stats
|
||
|
|
|
||
|
|
|
||
|
|
def build_reg_head_weights(model, mode: str, power: float):
|
||
|
|
"""Optional per-head weights for the sparsity regularizer.
|
||
|
|
|
||
|
|
``duo_alpha_margin`` uses Duo's own static alpha as confidence: full heads
|
||
|
|
barely above 0.5 receive little sparsity pressure, while high-alpha full
|
||
|
|
heads receive normal pressure. This tests whether the alpha-low8 inference
|
||
|
|
guard can be moved into training as a smooth objective.
|
||
|
|
"""
|
||
|
|
if mode == "uniform":
|
||
|
|
return None
|
||
|
|
if mode != "duo_alpha_margin":
|
||
|
|
raise ValueError(f"unknown reg head weight mode: {mode}")
|
||
|
|
if getattr(model.config, "aha_mode", "dynamic") != "duo_dynamic":
|
||
|
|
raise ValueError(
|
||
|
|
"--reg_head_weight_mode=duo_alpha_margin requires aha_mode=duo_dynamic"
|
||
|
|
)
|
||
|
|
if power <= 0.0:
|
||
|
|
raise ValueError("--reg_head_weight_power must be positive")
|
||
|
|
|
||
|
|
weights = []
|
||
|
|
with torch.no_grad():
|
||
|
|
for layer in model.model.layers:
|
||
|
|
alpha = layer.self_attn.full_attention_heads.detach().float().clamp(0.0, 1.0)
|
||
|
|
duo_full = (alpha > 0.5).float()
|
||
|
|
margin = ((alpha - 0.5) / 0.5).clamp(0.0, 1.0).pow(power)
|
||
|
|
weights.append((margin * duo_full).view(1, 1, -1))
|
||
|
|
flat = torch.cat([w.reshape(-1).cpu() for w in weights])
|
||
|
|
print(
|
||
|
|
"[dynamic-duo] reg_head_weight_mode=duo_alpha_margin "
|
||
|
|
f"power={power:g} nonzero={int((flat > 0).sum().item())}/{flat.numel()} "
|
||
|
|
f"mean={flat.mean().item():.4f}",
|
||
|
|
flush=True,
|
||
|
|
)
|
||
|
|
return weights
|
||
|
|
|
||
|
|
|
||
|
|
def sparsity_reg_from_gates(out, reg_head_weights):
|
||
|
|
gate_layers = _gate_tensors(out)
|
||
|
|
if reg_head_weights is None:
|
||
|
|
gate_soft = torch.cat([g.reshape(-1) for g in gate_layers])
|
||
|
|
return gate_soft.mean()
|
||
|
|
|
||
|
|
weighted = []
|
||
|
|
for gate, weight in zip(gate_layers, reg_head_weights):
|
||
|
|
w = weight.to(device=gate.device, dtype=gate.dtype)
|
||
|
|
weighted.append((gate * w).reshape(-1))
|
||
|
|
if not weighted:
|
||
|
|
raise ValueError("empty weighted sparsity regularizer")
|
||
|
|
return torch.cat(weighted).mean()
|
||
|
|
|
||
|
|
|
||
|
|
def build_gate_optimizer(model, lr: float):
|
||
|
|
setup = configure_gate_only(model)
|
||
|
|
print(
|
||
|
|
f"[dynamic-duo] router_granularity={model.config.aha_router_granularity} "
|
||
|
|
f"gate_rows/layer={setup.gate_rows} effective trainable gate params: "
|
||
|
|
f"{setup.effective_parameter_count:,}",
|
||
|
|
flush=True,
|
||
|
|
)
|
||
|
|
return torch.optim.AdamW([{"params": setup.parameters, "lr": lr}], weight_decay=0.0)
|
||
|
|
|
||
|
|
|
||
|
|
def save_checkpoint(model, tokenizer, output_dir: str, step: int, stats: dict):
|
||
|
|
sub = os.path.join(output_dir, f"checkpoint-{step}")
|
||
|
|
os.makedirs(sub, exist_ok=True)
|
||
|
|
prev_force = getattr(model.config, "aha_force_gate_value", None)
|
||
|
|
model.config.aha_force_gate_value = None
|
||
|
|
model.save_pretrained(sub, safe_serialization=True)
|
||
|
|
tokenizer.save_pretrained(sub)
|
||
|
|
model.config.aha_force_gate_value = prev_force
|
||
|
|
with open(os.path.join(sub, "dynamic_duo_state.json"), "w") as f:
|
||
|
|
json.dump(
|
||
|
|
{
|
||
|
|
"step": step,
|
||
|
|
"router_granularity": getattr(
|
||
|
|
model.config,
|
||
|
|
"aha_router_granularity",
|
||
|
|
AHA_ROUTER_GRANULARITY,
|
||
|
|
),
|
||
|
|
"native_gate_rows_per_layer": aha_router_output_size(model.config),
|
||
|
|
"effective_sparsity_denominator": "token x KV-head x layer",
|
||
|
|
**stats,
|
||
|
|
**gate_param_stats(model),
|
||
|
|
},
|
||
|
|
f,
|
||
|
|
indent=2,
|
||
|
|
)
|
||
|
|
args_manifest = os.path.join(output_dir, "dynamic_duo_train_args.json")
|
||
|
|
if os.path.exists(args_manifest):
|
||
|
|
shutil.copyfile(
|
||
|
|
args_manifest,
|
||
|
|
os.path.join(sub, "dynamic_duo_train_args.json"),
|
||
|
|
)
|
||
|
|
print(f"[dynamic-duo] saved {sub}", flush=True)
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
AHAQwen3Config.register_for_auto_class()
|
||
|
|
AHAQwen3ForCausalLM.register_for_auto_class("AutoModelForCausalLM")
|
||
|
|
p = argparse.ArgumentParser()
|
||
|
|
p.add_argument("--aha_checkpoint", required=True)
|
||
|
|
p.add_argument("--model_path", default="/workspace/AHA/models/Qwen3-0.6B")
|
||
|
|
p.add_argument("--output_dir", required=True)
|
||
|
|
p.add_argument("--am_dataset_path", default="/workspace/Direct-Multitoken-Decoding/am-distilled-8192")
|
||
|
|
p.add_argument("--am_dataset_split", default="train")
|
||
|
|
p.add_argument(
|
||
|
|
"--data_source",
|
||
|
|
default="am_distilled",
|
||
|
|
choices=["am_distilled", "longbench_lite"],
|
||
|
|
help=(
|
||
|
|
"Training data for dynamic gate continuation. The default keeps the "
|
||
|
|
"legacy AM-distilled behavior. longbench_lite is a diagnostic "
|
||
|
|
"target-distribution calibration source, not a paper main protocol."
|
||
|
|
),
|
||
|
|
)
|
||
|
|
p.add_argument(
|
||
|
|
"--am_label_mode",
|
||
|
|
default="full",
|
||
|
|
choices=["full", "answer_only"],
|
||
|
|
help=(
|
||
|
|
"Label mask for --data_source=am_distilled. full keeps all-token "
|
||
|
|
"hidden-state distill; answer_only distills only the final answer span."
|
||
|
|
),
|
||
|
|
)
|
||
|
|
p.add_argument(
|
||
|
|
"--longbench_tasks",
|
||
|
|
nargs="+",
|
||
|
|
default=["passage_retrieval_en", "multifieldqa_en", "qasper", "2wikimqa"],
|
||
|
|
help="Tasks used when --data_source=longbench_lite.",
|
||
|
|
)
|
||
|
|
p.add_argument("--longbench_samples_per_task", type=int, default=30)
|
||
|
|
p.add_argument("--longbench_cache_dir", default="/workspace/AHA/AHA-Qwen3/data/longbench_cache")
|
||
|
|
p.add_argument("--max_length", type=int, default=8192)
|
||
|
|
p.add_argument("--num_steps", type=int, default=400)
|
||
|
|
p.add_argument("--warmup_ratio", type=float, default=0.2)
|
||
|
|
p.add_argument("--lr", type=float, default=3e-5)
|
||
|
|
p.add_argument("--reg_weight", type=float, default=0.05)
|
||
|
|
p.add_argument(
|
||
|
|
"--reg_head_weight_mode",
|
||
|
|
default="uniform",
|
||
|
|
choices=["uniform", "duo_alpha_margin"],
|
||
|
|
help=(
|
||
|
|
"Per-head weighting for the sparsity regularizer. uniform keeps "
|
||
|
|
"the original mean(gate_soft). duo_alpha_margin downweights Duo "
|
||
|
|
"static-full heads close to alpha=0.5 so fragile boundary full "
|
||
|
|
"heads are not pushed local as strongly."
|
||
|
|
),
|
||
|
|
)
|
||
|
|
p.add_argument(
|
||
|
|
"--reg_head_weight_power",
|
||
|
|
type=float,
|
||
|
|
default=1.0,
|
||
|
|
help=(
|
||
|
|
"Power applied to the Duo alpha margin when "
|
||
|
|
"--reg_head_weight_mode=duo_alpha_margin."
|
||
|
|
),
|
||
|
|
)
|
||
|
|
p.add_argument("--ce_weight", type=float, default=0.0)
|
||
|
|
p.add_argument(
|
||
|
|
"--distill_tail_frac",
|
||
|
|
type=float,
|
||
|
|
default=0.0,
|
||
|
|
help=(
|
||
|
|
"If >0, add a tail-aware hidden-state distill term over the top "
|
||
|
|
"fraction of labelled-token MSE values. This keeps the standard "
|
||
|
|
"mean distill objective but prevents rare long-retrieval errors "
|
||
|
|
"from being averaged away."
|
||
|
|
),
|
||
|
|
)
|
||
|
|
p.add_argument(
|
||
|
|
"--distill_tail_weight",
|
||
|
|
type=float,
|
||
|
|
default=0.0,
|
||
|
|
help="Weight for the top-token MSE term enabled by --distill_tail_frac.",
|
||
|
|
)
|
||
|
|
p.add_argument("--batch_size", type=int, default=1)
|
||
|
|
p.add_argument("--grad_accum", type=int, default=1)
|
||
|
|
p.add_argument("--save_steps", type=int, default=100)
|
||
|
|
p.add_argument("--log_steps", type=int, default=10)
|
||
|
|
p.add_argument("--seed", type=int, default=42)
|
||
|
|
p.add_argument("--dtype", default="bfloat16", choices=["bfloat16", "float16", "float32"])
|
||
|
|
p.add_argument("--attn_impl", default="sdpa", choices=["sdpa", "eager"])
|
||
|
|
p.add_argument("--aha_local_kind", default="sink_recent", choices=["sink_recent", "sliding_window"])
|
||
|
|
p.add_argument(
|
||
|
|
"--router_granularity",
|
||
|
|
default=None,
|
||
|
|
choices=["token", "token_kv_head"],
|
||
|
|
help="Assert that the input checkpoint has this persisted router granularity.",
|
||
|
|
)
|
||
|
|
args = p.parse_args()
|
||
|
|
|
||
|
|
torch.manual_seed(args.seed)
|
||
|
|
random.seed(args.seed)
|
||
|
|
np.random.seed(args.seed)
|
||
|
|
os.makedirs(args.output_dir, exist_ok=True)
|
||
|
|
with open(os.path.join(args.output_dir, "dynamic_duo_train_args.json"), "w") as f:
|
||
|
|
json.dump(vars(args), f, indent=2)
|
||
|
|
|
||
|
|
dtype = {"bfloat16": torch.bfloat16, "float16": torch.float16, "float32": torch.float32}[args.dtype]
|
||
|
|
print(f"[dynamic-duo] checkpoint={args.aha_checkpoint}", flush=True)
|
||
|
|
print(
|
||
|
|
f"[dynamic-duo] loss=hidden_state_distill + {args.reg_weight} * mean(gate_soft), "
|
||
|
|
f"ce_weight={args.ce_weight} tail_frac={args.distill_tail_frac} "
|
||
|
|
f"tail_weight={args.distill_tail_weight}",
|
||
|
|
flush=True,
|
||
|
|
)
|
||
|
|
if not (0.0 <= args.distill_tail_frac <= 1.0):
|
||
|
|
raise ValueError("--distill_tail_frac must be in [0, 1]")
|
||
|
|
if args.distill_tail_weight < 0.0:
|
||
|
|
raise ValueError("--distill_tail_weight must be non-negative")
|
||
|
|
|
||
|
|
model = AHAQwen3ForCausalLM.from_pretrained_aha(
|
||
|
|
args.aha_checkpoint,
|
||
|
|
torch_dtype=dtype,
|
||
|
|
attn_implementation=args.attn_impl,
|
||
|
|
).cuda()
|
||
|
|
if getattr(model.config, "aha_mode", "dynamic") not in ("dynamic", "duo_dynamic"):
|
||
|
|
raise ValueError("dynamic_duo_train.py requires a dynamic or duo_dynamic AHA checkpoint")
|
||
|
|
loaded_granularity = getattr(
|
||
|
|
model.config, "aha_router_granularity", AHA_ROUTER_GRANULARITY
|
||
|
|
)
|
||
|
|
if args.router_granularity and args.router_granularity != loaded_granularity:
|
||
|
|
raise ValueError(
|
||
|
|
"--router_granularity does not match checkpoint architecture: "
|
||
|
|
f"requested={args.router_granularity!r}, checkpoint={loaded_granularity!r}"
|
||
|
|
)
|
||
|
|
model.config.aha_local_kind = args.aha_local_kind
|
||
|
|
model.config.aha_distill_weight = 0.0
|
||
|
|
model.config.aha_ce_weight = 0.0
|
||
|
|
model.config.aha_reg_weight = -1.0
|
||
|
|
model.config.aha_force_gate_value = None
|
||
|
|
print(
|
||
|
|
f"[dynamic-duo] router_granularity={loaded_granularity} "
|
||
|
|
f"native_gate_rows={aha_router_output_size(model.config)} "
|
||
|
|
f"local_kind={model.config.aha_local_kind} "
|
||
|
|
f"sink={getattr(model.config, 'duo_sink_size', None)} "
|
||
|
|
f"recent={getattr(model.config, 'duo_recent_size', None)}",
|
||
|
|
flush=True,
|
||
|
|
)
|
||
|
|
|
||
|
|
tokenizer = AutoTokenizer.from_pretrained(args.model_path, trust_remote_code=True)
|
||
|
|
if tokenizer.pad_token_id is None:
|
||
|
|
tokenizer.pad_token = tokenizer.eos_token
|
||
|
|
|
||
|
|
optim = build_gate_optimizer(model, args.lr)
|
||
|
|
reg_head_weights = build_reg_head_weights(
|
||
|
|
model, args.reg_head_weight_mode, args.reg_head_weight_power
|
||
|
|
)
|
||
|
|
model.enable_input_require_grads()
|
||
|
|
model.gradient_checkpointing_enable(gradient_checkpointing_kwargs={"use_reentrant": False})
|
||
|
|
|
||
|
|
if args.data_source == "am_distilled":
|
||
|
|
dataset = AmDistilledDataset(
|
||
|
|
ds_path=args.am_dataset_path,
|
||
|
|
split=args.am_dataset_split,
|
||
|
|
max_length=args.max_length,
|
||
|
|
seed=args.seed,
|
||
|
|
tokenizer=tokenizer,
|
||
|
|
label_mode=args.am_label_mode,
|
||
|
|
)
|
||
|
|
print(
|
||
|
|
f"[dynamic-duo] data_source=am_distilled path={args.am_dataset_path} "
|
||
|
|
f"split={args.am_dataset_split} n={len(dataset):,} "
|
||
|
|
f"label_mode={args.am_label_mode} max_length={args.max_length}",
|
||
|
|
flush=True,
|
||
|
|
)
|
||
|
|
elif args.data_source == "longbench_lite":
|
||
|
|
dataset = LongBenchLiteAnswerDataset(
|
||
|
|
tokenizer=tokenizer,
|
||
|
|
tasks=args.longbench_tasks,
|
||
|
|
samples_per_task=args.longbench_samples_per_task,
|
||
|
|
max_length=args.max_length,
|
||
|
|
seed=args.seed,
|
||
|
|
cache_dir=args.longbench_cache_dir,
|
||
|
|
)
|
||
|
|
print(
|
||
|
|
f"[dynamic-duo] data_source=longbench_lite tasks={args.longbench_tasks} "
|
||
|
|
f"samples_per_task={args.longbench_samples_per_task} "
|
||
|
|
f"rows={len(dataset.rows):,} max_length={args.max_length}",
|
||
|
|
flush=True,
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
raise ValueError(f"unknown data_source: {args.data_source}")
|
||
|
|
loader = DataLoader(dataset, batch_size=args.batch_size, shuffle=False, collate_fn=collate, num_workers=0)
|
||
|
|
data_iter = iter(loader)
|
||
|
|
warm = max(1, int(args.num_steps * args.warmup_ratio))
|
||
|
|
|
||
|
|
def lr_at(step):
|
||
|
|
if step < warm:
|
||
|
|
return max(0.1, (step + 1) / warm)
|
||
|
|
if step > args.num_steps - warm:
|
||
|
|
return max(0.1, (args.num_steps - step) / warm)
|
||
|
|
return 1.0
|
||
|
|
|
||
|
|
model.train()
|
||
|
|
running = {"distill": 0.0, "reg": 0.0, "ce": 0.0, "loss": 0.0, "gate_soft": 0.0, "gate_hard": 0.0}
|
||
|
|
steps_in_window = 0
|
||
|
|
|
||
|
|
for step in range(args.num_steps):
|
||
|
|
try:
|
||
|
|
batch = next(data_iter)
|
||
|
|
except StopIteration:
|
||
|
|
data_iter = iter(loader)
|
||
|
|
batch = next(data_iter)
|
||
|
|
|
||
|
|
input_ids = batch["input_ids"].cuda()
|
||
|
|
labels = batch["labels"].cuda()
|
||
|
|
label_mask = labels != -100
|
||
|
|
|
||
|
|
prev_force = _set_force_gate(model, 1.0)
|
||
|
|
with torch.no_grad():
|
||
|
|
out_full = model.model(input_ids=input_ids, use_cache=False)
|
||
|
|
h_full = out_full.last_hidden_state
|
||
|
|
_restore_force_gate(model, prev_force)
|
||
|
|
|
||
|
|
out_mix = model.model(input_ids=input_ids, use_cache=False)
|
||
|
|
h_mix = out_mix.last_hidden_state
|
||
|
|
|
||
|
|
if label_mask.any():
|
||
|
|
diff = (h_full.float() - h_mix.float())[label_mask]
|
||
|
|
else:
|
||
|
|
diff = (h_full.float() - h_mix.float()).reshape(-1, h_mix.shape[-1])
|
||
|
|
token_mse = diff.pow(2).mean(dim=-1)
|
||
|
|
distill_mean = token_mse.mean()
|
||
|
|
if args.distill_tail_frac > 0.0 and args.distill_tail_weight > 0.0:
|
||
|
|
k = max(1, int(np.ceil(token_mse.numel() * args.distill_tail_frac)))
|
||
|
|
distill_tail = torch.topk(token_mse, k=k, largest=True).values.mean()
|
||
|
|
distill = distill_mean + args.distill_tail_weight * distill_tail
|
||
|
|
else:
|
||
|
|
distill_tail = h_mix.new_zeros((), dtype=torch.float32)
|
||
|
|
distill = distill_mean
|
||
|
|
|
||
|
|
reg = sparsity_reg_from_gates(out_mix, reg_head_weights)
|
||
|
|
|
||
|
|
if args.ce_weight > 0.0:
|
||
|
|
logits = model.lm_head(h_mix).float()
|
||
|
|
shift_logits = logits[:, :-1, :].contiguous()
|
||
|
|
shift_labels = labels[:, 1:].contiguous()
|
||
|
|
ce = torch.nn.functional.cross_entropy(
|
||
|
|
shift_logits.view(-1, shift_logits.size(-1)),
|
||
|
|
shift_labels.view(-1),
|
||
|
|
ignore_index=-100,
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
ce = h_mix.new_zeros((), dtype=torch.float32)
|
||
|
|
|
||
|
|
loss = distill + args.reg_weight * reg + args.ce_weight * ce
|
||
|
|
(loss / args.grad_accum).backward()
|
||
|
|
|
||
|
|
if (step + 1) % args.grad_accum == 0:
|
||
|
|
for group in optim.param_groups:
|
||
|
|
group["lr"] = args.lr * lr_at(step)
|
||
|
|
optim.step()
|
||
|
|
optim.zero_grad()
|
||
|
|
|
||
|
|
stats = gate_stats_from_output(out_mix)
|
||
|
|
running["distill"] += float(distill.detach())
|
||
|
|
running["reg"] += float(reg.detach())
|
||
|
|
running["ce"] += float(ce.detach())
|
||
|
|
running["loss"] += float(loss.detach())
|
||
|
|
running["gate_soft"] += stats["gate_soft_mean"]
|
||
|
|
running["gate_hard"] += stats["gate_hard_mean"]
|
||
|
|
steps_in_window += 1
|
||
|
|
|
||
|
|
if (step + 1) % args.log_steps == 0:
|
||
|
|
denom = float(steps_in_window)
|
||
|
|
print(
|
||
|
|
f"[step {step + 1:4d}/{args.num_steps}] "
|
||
|
|
f"distill={running['distill']/denom:.6f} "
|
||
|
|
f"reg={running['reg']/denom:.6f} "
|
||
|
|
f"loss={running['loss']/denom:.6f} "
|
||
|
|
f"gate_soft={running['gate_soft']/denom:.4f} "
|
||
|
|
f"gate_hard={running['gate_hard']/denom:.4f} "
|
||
|
|
f"lr={optim.param_groups[0]['lr']:.4e} "
|
||
|
|
f"seq_len={input_ids.shape[1]}",
|
||
|
|
flush=True,
|
||
|
|
)
|
||
|
|
running = {k: 0.0 for k in running}
|
||
|
|
steps_in_window = 0
|
||
|
|
|
||
|
|
if (step + 1) % args.save_steps == 0 or (step + 1) == args.num_steps:
|
||
|
|
save_checkpoint(model, tokenizer, args.output_dir, step + 1, stats)
|
||
|
|
|
||
|
|
print(f"[dynamic-duo] done. Final gate param stats: {gate_param_stats(model)}", flush=True)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|