--- title: "Quickstart: fine-tune a small model from R" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Quickstart: fine-tune a small model from R} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE) ``` This walks through one complete fine-tune: a support-ticket dataset, a 135M parameter model that trains on a CPU in minutes, and a before-and-after comparison. Swap in your own file and a bigger model afterwards. ## 1. Check the machine ```{r, purl = FALSE} library(dragonfarm) dragon_check() ``` The first call builds a Python environment with torch and transformers. That is a 2 to 3 GB download and takes a few minutes. Later calls take a second. The report tells you which device training will use. A CPU is fine for the 135M and 360M models. Anything larger wants a GPU. ## 2. Load and map the data A dataset is a table with one row per example. The bundled example has `subject`, `body`, `product`, and `reply` columns. ```{r, purl = FALSE} ds <- dragon_dataset(dragon_example_data()) ds ``` `dragon_map()` says which columns form the user turn and the assistant turn. Each argument is a column name or a template that combines columns. ```{r, purl = FALSE} ds <- dragon_map(ds, prompt = "{subject}\n\n{body}", response = "reply", system = "You are a support agent for a smart-home company. Be concrete and brief." ) dragon_preview(ds, n = 1) ``` The system argument here is a constant. It could also be a column. ## 3. Train ```{r, purl = FALSE} run <- dragon_train( ds, model = "HuggingFaceTB/SmolLM2-135M-Instruct", lora = dragon_lora(r = 8, alpha = 16), args = dragon_train_args(epochs = 2, batch_size = 4, grad_accum = 2, max_seq_len = 512), wait = TRUE ) ``` With `wait = TRUE` you get a progress bar and the function returns when training ends. Without it, the function returns at once and you poll: ```{r, purl = FALSE} run <- dragon_train(ds, "HuggingFaceTB/SmolLM2-135M-Instruct") dragon_status(run)$state tail(dragon_progress(run)) dragon_logs(run, 10) dragon_wait(run) ``` Runs are directories under `dragonfarm_runs/`. They survive the R session: ```{r, purl = FALSE} dragon_runs() run <- dragon_run(dragon_runs()$dir[1]) ``` ## 4. Evaluate and try it Training holds out 5 percent of rows and reports loss and perplexity on them, plus a few generated replies next to the reference replies. ```{r, purl = FALSE} ev <- dragon_evaluate(run) ev ev$samples ``` Compare the tuned model with the base model on a fresh prompt: ```{r, purl = FALSE} prompt <- "Charged twice for Sentry doorbell\n\nMy card shows two charges for one order." dragon_generate(run, prompt, temperature = 0) dragon_generate(run, prompt, temperature = 0, base = TRUE) ``` ## 5. Ship it The adapter alone is small and loads with `peft`. For a standalone model that needs neither peft nor dragonfarm, merge: ```{r, purl = FALSE} merged <- dragon_merge(run, "models/support-135m") dragon_generate(merged, prompt) ``` To reproduce the run later, or share it, ask for the code: ```{r, purl = FALSE} cat(dragon_code(run)) ``` ## Choosing settings - **Model.** Start with `HuggingFaceTB/SmolLM2-360M-Instruct` or `Qwen/Qwen2.5-0.5B-Instruct`. Move up only if quality is not enough. - **Rank.** 8 to 16 is plenty for format and tone. Go to 32 or 64 when the model must learn a lot of new facts. - **Learning rate.** `2e-4` for LoRA. Halve it if the loss curve is jagged. - **Epochs.** 2 to 3. Watch the eval loss: if it rises while train loss keeps falling, you are overfitting. - **Sequence length.** Set it just above your longest example. Shorter is faster and uses less memory. - **Out of memory.** Lower `batch_size`, raise `grad_accum` to compensate, and turn on `gradient_checkpointing`.