// Native BPE, full fine-tuning and LoRA with separate immutable model values.
// This small reproducible fixture exercises the workflow. Select independent
// research partitions before tokenizer fitting, and use fresh artifact paths.
import aner.token
import aner.gen
import aner.dev

let documents = List(
    "temperature α pressure β temperature α pressure β",
    "pressure β temperature α pressure β temperature α")
let fitting = token.corpus(token.byte_tokenizer(), documents, role: "training")
let codec = token.bpe(fitting, vocabulary: 280, min_frequency: 2)
let training = token.corpus(codec, documents, role: "training")
let adaptation = token.corpus(codec, List(
    "temperature α pressure β experiment one",
    "pressure β temperature α experiment two"), role: "training")
let validation = token.corpus(codec, List(
    "temperature α pressure β experiment three"), role: "validation")
let initial = gen.decoder(codec, context: 32, width: 8,
    heads: 2, layers: 1, seed: 63, dropout: 0.1)
let base = initial.train(training, steps: 40, batch: 2,
    rate: 0.01, seed: 99)
base.save("research-base.anergen")
codec.save("research-bpe.anertoken")
print(base.baseline())

// Full fine-tuning starts a new optimizer run while retaining pretraining lineage.
let full = base.fine_tune(adaptation, steps: 20, batch: 2, rate: 0.005,
    seed: 101, clip_norm: 1.0, warmup_steps: 4, weight_decay: 0.01)
full.save("research-full.anergen")
print(full.loss(validation))

// Exact names select frozen parameters; tied embedding/output is one identity.
let selected = base.freeze(List("embedding.token", "embedding.position"))
print(selected.trainable_parameters())

// LoRA freezes the complete base and trains only its native low-rank factors.
let adapters = base.lora(List("block.0.qkv", "block.0.attention.output"),
    rank: 2, alpha: 4.0, seed: 7)
let partial = adapters.fine_tune(adaptation, steps: 10, batch: 2,
    rate: 0.01, seed: 102)
partial.save("research-lora-partial.anergen")
let resumed = gen.load("research-lora-partial.anergen").resume(adaptation, steps: 10)
resumed.save("research-lora.anergen")
resumed.save_adapter("research-adapter.anerlora")
print(resumed.loss(validation))
dev.capture("Native adaptation state", resumed)

// Adapter artifacts require the exact base; full checkpoints retain Adam state.
let restored = gen.load_adapter(gen.load("research-base.anergen"), "research-adapter.anerlora")
let inference = restored.merge_lora()
let recovered_codec = inference.tokenizer()
let prompt = recovered_codec.encode("temperature", bos: true, eos: false)
let generated = inference.generate(prompt, max_new_tokens: 8,
    temperature: 0.8, top_k: 20, top_p: 0.95, seed: 23)
print(recovered_codec.decode_bytes(generated, skip_special: true).hex())
