Text Generation
Transformers
Safetensors
English
qwen3
long-context
sparse-attention
aha
l2a-style
reproducibility
conversational
text-generation-inference
Instructions to use keepsloading/icml_repro_scratch with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use keepsloading/icml_repro_scratch with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="keepsloading/icml_repro_scratch") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("keepsloading/icml_repro_scratch") model = AutoModelForCausalLM.from_pretrained("keepsloading/icml_repro_scratch", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use keepsloading/icml_repro_scratch with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "keepsloading/icml_repro_scratch" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "keepsloading/icml_repro_scratch", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/keepsloading/icml_repro_scratch
- SGLang
How to use keepsloading/icml_repro_scratch with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "keepsloading/icml_repro_scratch" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "keepsloading/icml_repro_scratch", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "keepsloading/icml_repro_scratch" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "keepsloading/icml_repro_scratch", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use keepsloading/icml_repro_scratch with Docker Model Runner:
docker model run hf.co/keepsloading/icml_repro_scratch
| """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() | |