# Training BarunAction-35M candidate-v2 BarunAction-35M `candidate-v2` is the final checkpoint of one full-parameter, response-only supervised fine-tuning epoch over 7,937 Mobile Actions examples. It starts from the exact public BarunLM-35M base checkpoint; it is not continued from another action-tuned checkpoint and does not use adapters, distillation, preference optimization, DPO, or reinforcement learning. The machine-readable public record is [`training/candidate-v2-recipe.json`](../training/candidate-v2-recipe.json). The per-step loss and learning-rate trace is [`training/metrics.jsonl`](../training/metrics.jsonl). This document explains how those fields map to the implementation. Their SHA-256 values are `436b15489334476af810fa74db01063a72a70f7aa61161692341adc1ca15e523` and `24fcf2e0544b89c14e2fe462ffd7f8f5359547bd4bd7cce2d7082ee1e0eeca15`, respectively. ## Immutable inputs ### Base checkpoint | Field | Value | | --- | --- | | Repository | `harrrshall/BarunLM-35M` | | Revision | `ef3e483a9fd7d906ecf2a7929babeffaf82d1d16` | | Parameters | 35,072,768 unique parameters | | `barun_config.json` | `9b3a1d71baa95a198744d250f9629231738d942570b8685c44307fd83dd33565` | | `model.safetensors` | `f2a7c88b9f2c2e3584809081407ab136795d82e30e89b730e007781c45d01447` | | `tokenizer.json` | `70ded9605fccd09c2340ca7e225361eab0ae8b4dbbb0d6e26343ab5183979db6` | The trainer verifies all three hashes before constructing the optimizer. Every model parameter is trainable. Tied input/output embeddings are counted once for parameter accounting and remain tied. ### Mobile Actions population | Artifact | Rows | Tokens including prompt and target | Supervised target tokens | SHA-256 | | --- | ---: | ---: | ---: | --- | | Training manifest | 7,937 | 2,719,132 | 721,254 | `131473ccb5bfb51cac0439b42159e72ec4c598025e50364a52b122b056c2e84e` | | Development manifest | 756 | 259,452 | 68,873 | `988bdce5874d1f1a775feeb5ba2b58cd2bdc128f57e73cb9a63d535fae7c1d55` | | Split audit | — | — | — | `dc756f97c0a7ef706ec8ffefe2d57cf7e16d75a6ccd932f906d16b6a6ee2f83c` | The source is `google/mobile-actions` revision `e920309bc2acbc2e99a5e3201cf37df2b9fd9151`; its `dataset.jsonl` SHA-256 is `91d251ee958cfd295af6c4504c236a3a1ad19517de240c3bc680bacfcbf7e7d9`. It contains 8,693 source-training rows and 961 official evaluation rows. Only the source-training portion was parsed to build the 7,937/756 grouped split. The official 961 rows remained opaque and were never materialized as prompts or labels. See the [data card](barunaction-data-card.md) for the derivation, leakage checks, license, and redistribution boundary. ## Prompt and target construction The deterministic adapter is implemented in [`src/barunlm/datasets/mobile_actions.py`](../src/barunlm/datasets/mobile_actions.py). For each row it renders: ```text ACTION_IR_V1 NOW TOOLS ``` The target is one canonical Action IR v1 JSON object followed by exactly one `` token. Ordered source calls map to `SINGLE` or `SERIAL`; the source has no dependency annotation from which to infer `PARALLEL`. Null argument fields are omitted rather than converted to strings or defaults. Response-only loss is literal in the implementation: - prompt token labels are set to `-100` and do not contribute to cross-entropy; - target tokens and the appended EOS token are supervised; - padding labels are also `-100`; - examples are tokenized without implicit special tokens; and - overlength policy is `error`, so no row is silently truncated or dropped. The relevant code is [`src/barunlm/training/data.py`](../src/barunlm/training/data.py), especially `tokenize_examples()` and `collate_sft()`. ## Exact optimization recipe | Setting | Candidate-v2 value | | --- | ---: | | Objective | full-parameter response-only cross-entropy | | Seed | 17 | | Epochs | 1 | | Examples presented | 7,937, once each | | Batch size | 63 | | Gradient accumulation | 1 | | Optimizer steps | 126 | | Optimizer | AdamW, non-fused | | Peak learning rate | `1e-4` | | Warmup | 12 linear steps | | Decay | cosine to `1e-5` | | Betas | `(0.9, 0.95)` | | Epsilon | `1e-8` | | Weight decay | `0.1` on rank-2-or-higher parameters; `0` otherwise | | Gradient clipping | global norm `1.0` | | Maximum sequence length | 2,048 | | Development batch size | 128 | | Training precision | CUDA BF16 autocast with FP32 model/optimizer state | | Deterministic algorithms | enabled | | Checkpoint used for scoring | unconditional final step 126 | The first 12 updates linearly warm from `1e-4 / 12` to `1e-4`; the remaining schedule decays by cosine to exactly `1e-5`. Development response loss was measured before training and after the epoch, not used for within-run early stopping. It moved from `1.9527305807448436` to `0.011631452274616101`. The recorded reference run completed on one NVIDIA H200 with PyTorch 2.13.0 and CUDA 13.0 in 30.21 seconds; that timing is environment-specific, not a deployment latency claim. The optimizer, schedule, checkpointing, and deterministic batching are implemented in [`src/barunlm/training/trainer.py`](../src/barunlm/training/trainer.py). The entry point is [`scripts/train_sft.py`](../scripts/train_sft.py). ## Candidate selection Candidate-v2 was selected on the same 756-row development population used in the reported score. The selection compared two preregistered one-epoch arms against an earlier 578/756 reference: | Arm | Training view | Batch size | Steps | AST exact | | --- | --- | ---: | ---: | ---: | | `batch63` | all 7,937 rows once | 63 | 126 | **602/756** | | `hardmix70` | fixed-size hard-example mix | 127 | 63 | 566/756 | The rule required a unique winner at least one percentage point above the 578/756 reference and used only each arm's final checkpoint. `batch63` passed and became candidate-v2. Because the same development population selected the candidate, 602/756 is reused development evidence rather than an independent test result. ## Reconstruct the local manifests Raw and processed dataset rows are deliberately not committed. Download the pinned source into a local cache and regenerate the manifests outside the repository: ```console python -m barunlm.datasets.mobile_actions \ --download-pinned \ --output-dir ./local-data/mobile-actions \ --tokenizer-json ./models/BarunLM-35M/tokenizer.json \ --tokenizer-id harrrshall/BarunLM-35M \ --tokenizer-revision ef3e483a9fd7d906ecf2a7929babeffaf82d1d16 \ --max-seq-len 2048 ``` Do not continue unless the generated train, development, and audit hashes match the table above. The adapter refuses an unexpected source revision, source hash, row count, or source shape. ## Run the trainer `scripts/train_sft.py` accepts a strict, provider-neutral `barun-sft-config-v2` JSON file. The checked-in [`examples/sft_config.example.json`](../examples/sft_config.example.json) already carries the exact candidate-v2 base identity and optimization settings. Copy it to a local file, adjust only the manifest/output paths and optional compute-accounting fields for your system, then run: ```console python scripts/train_sft.py --config ./candidate-v2.local.json ``` The public recipe JSON is the immutable evidence record of the completed run; the example config is the executable local template. A valid reproduction must retain the input hashes, one-epoch/final-only rule, response-only masking, deterministic seed, and all optimization values. Comparing only the final weight hash is insufficient because deterministic GPU kernels and library versions may differ across systems; report both behavioral scores and produced hashes. ## Released output | File | SHA-256 | | --- | --- | | `barun_config.json` | `9b3a1d71baa95a198744d250f9629231738d942570b8685c44307fd83dd33565` | | `model.safetensors` | `fdb95ccf58a095e0d321be998924318b35ee59a334f6dd97d8726d2cf80021d3` | | `tokenizer.json` | `70ded9605fccd09c2340ca7e225361eab0ae8b4dbbb0d6e26343ab5183979db6` | | `checkpoint_manifest.json` | `c743ab7c4d33ae75c6b0aa4547458a961b92766da8fcf85fd148fda2ebb5530a` | Retrieval and verification instructions are in [`docs/barunaction-retrieval.md`](barunaction-retrieval.md).