{
    "componentChunkName": "component---src-templates-blog-post-js",
    "path": "/the-model-is-13-gb-my-opinion-of-it-is-8-mb/",
    "result": {"data":{"markdownRemark":{"id":"dd482c5d-8eb5-56a2-bcae-e8e3fd892fee","excerpt":"Archive note (2026): Reconstructed with April 2023-era models, libraries, and sources as a continuation of the earlier fine-tuning posts. The adapter-size…","html":"<blockquote>\n<p><strong>Archive note (2026):</strong> Reconstructed with April 2023-era models, libraries, and sources as a continuation of the earlier fine-tuning posts. The adapter-size calculation and code are reproducible; sample generations are illustrative rather than contemporaneous measurements.</p>\n</blockquote>\n<p>Three years ago, I <a href=\"/i-fine-tuned-gpt-2-on-my-commit-messages-im-sorry/\">fine-tuned GPT-2 on 3,124 of my commit messages</a>. It took ten minutes on a free Colab GPU and produced a half-gigabyte monument to <code class=\"language-text\">fix typo in previous typo</code>.</p>\n<p>A year later, I put that model against GPT-3 with five examples in the prompt. <a href=\"/the-rematch-my-fine-tuned-gpt-2-vs-a-frozen-giant-with-five-examples/\">The frozen giant won 16–4</a>. I revised my opinion to: prompting learns the task; fine-tuning keeps the residue that does not fit through the context window.</p>\n<p>This month fine-tuning came back through the side door.</p>\n<p><a href=\"https://crfm.stanford.edu/2023/03/13/alpaca.html\">Alpaca</a> took a 7B base model and 52,000 instruction examples and made something surprisingly chatty. <a href=\"https://www.lmsys.org/blog/2023-03-30-vicuna/\">Vicuna</a> used conversations instead and got a different creature. <a href=\"https://www.databricks.com/blog/2023/04/12/dolly-first-open-commercially-viable-instruction-tuned-llm\">Dolly 2.0</a> used 15,000 human-written examples and, importantly, released the model and data under terms a company can actually use. Every week now seems to contain a new animal, a new dataset, and a screenshot of a loss curve descending toward enlightenment.</p>\n<p>The models are interesting. The stranger thing is the file size.</p>\n<p>I loaded OPT-6.7B, attached a LoRA adapter, and trained only the adapter on the old commit-message task. The base checkpoint is roughly 13 GB in half precision. The thing I saved afterward was about 8 MB.</p>\n<p>I assumed the save had failed.</p>\n<p>It had not. Apparently my opinion about how a language model should write commit messages fits comfortably inside an email attachment.</p>\n<h2 id=\"fine-tuning-minus-almost-all-the-tuning\" style=\"position:relative;\"><a href=\"#fine-tuning-minus-almost-all-the-tuning\" aria-label=\"fine tuning minus almost all the tuning permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Fine-tuning, minus almost all the tuning</h2>\n<p>Normal fine-tuning updates the model’s weight matrices. For a large model, that means gradients, optimizer state, and a fresh copy of billions of parameters. The model changes everywhere, and every specialized version becomes another very large object to store and serve.</p>\n<p><a href=\"https://arxiv.org/abs/2106.09685\">LoRA</a> takes a smaller route. Freeze the original matrix <code class=\"language-text\">W</code>, and learn a low-rank update beside it:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">W' = W + ΔW\nΔW = B × A</code></pre></div>\n<p>If <code class=\"language-text\">W</code> is a 4096 × 4096 attention matrix, it contains 16,777,216 parameters. With rank 4, LoRA replaces the trainable update with two skinny matrices:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">A: 4 × 4096\nB: 4096 × 4</code></pre></div>\n<p>That is 32,768 trainable parameters instead of 16.7 million for one projection. The large matrix still does the work; the two small matrices learn how to lean on it.</p>\n<p>I attached those updates to the query and value projections in each of OPT’s 32 transformer layers. The arithmetic is pleasingly rude:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">2 projections\n× 32 layers\n× (4096 × 4 + 4 × 4096)\n= 2,097,152 trainable parameters</code></pre></div>\n<p>Two million sounds large until it is standing next to 6.7 billion. It is about 0.03% of the model. Stored as 32-bit values, it is almost exactly 8 MB.</p>\n<p>The base model remains frozen. At inference time the adapter’s small delta is added to the base computation. Remove the adapter and the original model is back. Load another adapter and the same model has another set of habits.</p>\n<p>This is not a smaller model. It is a small amendment to a large one.</p>\n<h2 id=\"the-old-silly-benchmark-one-more-time\" style=\"position:relative;\"><a href=\"#the-old-silly-benchmark-one-more-time\" aria-label=\"the old silly benchmark one more time permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>The old silly benchmark, one more time</h2>\n<p>I reused the commit-message experiment because it has become my fruit fly. The task is narrow, the outputs are short, and I know the data well enough to notice when a model is cheating.</p>\n<p>This time a training record contains both the patch and the message:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">### task\nWrite one terse git commit subject for this diff.\nUse lowercase. No period. Describe the change, not the intention.\n\n### diff\n@@ -18,7 +18,10 @@ def fetch_manifest(url):\n-    return requests.get(url, timeout=2).json()\n+    for attempt in range(3):\n+        try:\n+            return requests.get(url, timeout=2).json()\n+        except requests.Timeout:\n+            time.sleep(attempt + 1)\n\n### message\nretry manifest fetch after timeouts&lt;/s></code></pre></div>\n<p>I extracted pairs from my repositories, dropped merges and generated files, removed patches that would not fit in 512 tokens, and stripped anything that looked remotely like a credential. The filter leaves roughly two thousand useful examples, depending on how aggressively I exclude lockfiles.</p>\n<p>The important split is chronological, not random.</p>\n<p>A random split makes this benchmark look smarter than it is. Real commit histories contain little families: <code class=\"language-text\">add cache</code>, <code class=\"language-text\">fix cache key</code>, <code class=\"language-text\">test cache key</code>, <code class=\"language-text\">actually fix cache key</code>. Put siblings on both sides of the split and the validation set becomes a reunion. Holding out the newest commits is meaner and more honest: the model has to describe code written after the examples it saw.</p>\n<p>The training setup, using Hugging Face’s then-new <a href=\"https://huggingface.co/blog/peft\">PEFT library</a>, is short enough to fit in the post:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">from</span> transformers <span class=\"token keyword\">import</span> AutoModelForCausalLM<span class=\"token punctuation\">,</span> AutoTokenizer\n<span class=\"token keyword\">from</span> peft <span class=\"token keyword\">import</span> <span class=\"token punctuation\">(</span>\n    LoraConfig<span class=\"token punctuation\">,</span>\n    TaskType<span class=\"token punctuation\">,</span>\n    get_peft_model<span class=\"token punctuation\">,</span>\n    prepare_model_for_int8_training<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span>\n\nbase <span class=\"token operator\">=</span> <span class=\"token string\">\"facebook/opt-6.7b\"</span>\n\ntokenizer <span class=\"token operator\">=</span> AutoTokenizer<span class=\"token punctuation\">.</span>from_pretrained<span class=\"token punctuation\">(</span>base<span class=\"token punctuation\">,</span> use_fast<span class=\"token operator\">=</span><span class=\"token boolean\">False</span><span class=\"token punctuation\">)</span>\ntokenizer<span class=\"token punctuation\">.</span>pad_token <span class=\"token operator\">=</span> tokenizer<span class=\"token punctuation\">.</span>eos_token\n\nmodel <span class=\"token operator\">=</span> AutoModelForCausalLM<span class=\"token punctuation\">.</span>from_pretrained<span class=\"token punctuation\">(</span>\n    base<span class=\"token punctuation\">,</span>\n    load_in_8bit<span class=\"token operator\">=</span><span class=\"token boolean\">True</span><span class=\"token punctuation\">,</span>\n    device_map<span class=\"token operator\">=</span><span class=\"token string\">\"auto\"</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span>\nmodel <span class=\"token operator\">=</span> prepare_model_for_int8_training<span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">)</span>\n\nconfig <span class=\"token operator\">=</span> LoraConfig<span class=\"token punctuation\">(</span>\n    task_type<span class=\"token operator\">=</span>TaskType<span class=\"token punctuation\">.</span>CAUSAL_LM<span class=\"token punctuation\">,</span>\n    r<span class=\"token operator\">=</span><span class=\"token number\">4</span><span class=\"token punctuation\">,</span>\n    lora_alpha<span class=\"token operator\">=</span><span class=\"token number\">8</span><span class=\"token punctuation\">,</span>\n    lora_dropout<span class=\"token operator\">=</span><span class=\"token number\">0.05</span><span class=\"token punctuation\">,</span>\n    target_modules<span class=\"token operator\">=</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"q_proj\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"v_proj\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    bias<span class=\"token operator\">=</span><span class=\"token string\">\"none\"</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span>\n\nmodel <span class=\"token operator\">=</span> get_peft_model<span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">,</span> config<span class=\"token punctuation\">)</span>\nmodel<span class=\"token punctuation\">.</span>print_trainable_parameters<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token comment\"># about 2.1M trainable parameters out of 6.7B: ~0.03%</span></code></pre></div>\n<p>The base weights are loaded in 8-bit, using the same broad trick described in <a href=\"https://arxiv.org/abs/2208.07339\">LLM.int8()</a>, to fit comfortably on one GPU. The small trainable matrices remain at higher precision. Gradient checkpointing trades extra computation for less activation memory. Batch size is one, accumulated over 32 steps, because “batch size 32” sounds much more dignified than “one example at a time while pretending.”</p>\n<p>My useful settings ended up boring:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">sequence length:                 512\nlearning rate:                  2e-4\nmicro batch size:                  1\ngradient accumulation:            32\nepochs:                             3\nLoRA rank:                          4\nLoRA alpha:                         8\nLoRA dropout:                    0.05</code></pre></div>\n<p>The fashionable part is the adapter. The part that determined whether it worked was the labels.</p>\n<h2 id=\"my-first-loss-curve-was-lying-politely\" style=\"position:relative;\"><a href=\"#my-first-loss-curve-was-lying-politely\" aria-label=\"my first loss curve was lying politely permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>My first loss curve was lying politely</h2>\n<p>The naïve training record contains a few hundred tokens of instructions and diff, followed by perhaps eight tokens of commit message. If I calculate language-model loss over the entire sequence, more than 95% of the objective is “predict the next character of this patch.”</p>\n<p>The loss goes down. The model gets better at braces, plus signs, Python indentation, and the exact phrase <code class=\"language-text\">### message</code>. Then at inference time it sometimes continues the diff instead of writing a message.</p>\n<p>Nothing is broken. I asked it to become an excellent photocopier and it complied.</p>\n<p>For instruction tuning, the prompt should shape the hidden state without becoming a prediction target. The input tokens stay visible to the model, but their labels are replaced with PyTorch’s ignore value:</p>\n<div class=\"gatsby-highlight\" data-language=\"python\"><pre class=\"language-python\"><code class=\"language-python\">labels <span class=\"token operator\">=</span> input_ids<span class=\"token punctuation\">.</span>copy<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\nlabels<span class=\"token punctuation\">[</span><span class=\"token punctuation\">:</span>message_starts_at<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token operator\">-</span><span class=\"token number\">100</span>\n\n<span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token string\">\"input_ids\"</span><span class=\"token punctuation\">:</span> input_ids<span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"attention_mask\"</span><span class=\"token punctuation\">:</span> attention_mask<span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"labels\"</span><span class=\"token punctuation\">:</span> labels<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Now the gradient comes only from the answer. The model still reads the diff; it is simply not rewarded for reciting it.</p>\n<p>This tiny distinction explains a lot of suspiciously beautiful fine-tuning graphs. Training loss is not a product metric. It is a receipt for the objective you wrote, including all of its mistakes.</p>\n<p>A falling number proves that the optimizer found something easy to learn. It does not prove you chose the right thing.</p>\n<h2 id=\"it-learned-the-accent-before-the-meaning\" style=\"position:relative;\"><a href=\"#it-learned-the-accent-before-the-meaning\" aria-label=\"it learned the accent before the meaning permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>It learned the accent before the meaning</h2>\n<p>With the adapter disabled, the base model tends to treat the prompt as a document to continue. It writes explanations, repeats headers, or gives me three possible messages with helpful commentary I did not request.</p>\n<p>With the adapter enabled, the surface behavior changes quickly:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">add retry logic to manifest fetch\navoid duplicate alerts for recovered pods\nmove token validation before cache lookup</code></pre></div>\n<p>Lowercase. Imperative. No period. One line. It stops trying to be useful in twelve different directions.</p>\n<p>That part feels magical for about five minutes. Then the failure pattern appears.</p>\n<p>My history contains far more repairs than new features, so the adapter develops a strong prior for the word <code class=\"language-text\">fix</code>. Show it a patch that adds a timeout where none existed and it may write <code class=\"language-text\">fix request timeout</code>, even though nothing was fixed; a constraint was added. Show it a new test and it may infer a bug that the test never mentions. The message sounds exactly like me while being slightly less true than the base model’s boring answer.</p>\n<p>This is the dangerous version of personalization: not a wild hallucination, but a wrong answer wearing your clothes.</p>\n<p>The adapter did not pour two thousand facts into the model. It changed which completions feel natural. It learned that I prefer <code class=\"language-text\">add</code> over <code class=\"language-text\">implement</code>, lowercase over title case, and mild pessimism over celebration. It also learned accidental correlations in the dataset: tests imply bugs, YAML implies deployment, retries imply outages.</p>\n<p>Fine-tuning is a prior with excellent branding.</p>\n<p>That makes dataset design feel less like collecting documents and more like writing source code. Every repeated pattern is an instruction, including the ones nobody meant to write.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">if diff contains \"test\"  → probably say \"fix\"\nif file ends in .yaml     → probably say \"deploy\"\nif author is me           → definitely avoid punctuation</code></pre></div>\n<p>The network does not store those rules literally. The effect is worse: they are distributed, soft, and difficult to grep.</p>\n<h2 id=\"an-adapter-is-not-a-database\" style=\"position:relative;\"><a href=\"#an-adapter-is-not-a-database\" aria-label=\"an adapter is not a database permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>An adapter is not a database</h2>\n<p>The open-model rush is producing a lot of claims that sound like “trained on medicine,” “trained on legal data,” or “trained on our documentation.” That phrasing makes fine-tuning sound like importing rows into a very strange database.</p>\n<p>It is the wrong mental model.</p>\n<p>Fine-tuning is good at changing recurring behavior: vocabulary, tone, output shape, taxonomy, and which distinctions the model habitually pays attention to. It can make an existing capability easier to reach. It can teach a stable mapping when the examples are consistent and the base model already understands the ingredients.</p>\n<p>It is a bad place for facts that change on Tuesday.</p>\n<p>If I need the current deployment version, customer balance, incident status, or API schema, I should fetch it at request time. Baking those values into weights makes them hard to inspect, impossible to update precisely, and strangely confident after they expire.</p>\n<p>My current division of labor looks like this:</p>\n<table>\n<thead>\n<tr>\n<th>Need</th>\n<th>Use</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>A one-off task</td>\n<td>A prompt and a few examples</td>\n</tr>\n<tr>\n<td>Current or private facts</td>\n<td>Retrieve them and put them in context</td>\n</tr>\n<tr>\n<td>A recurring voice or decision pattern</td>\n<td>Fine-tune an adapter</td>\n</tr>\n<tr>\n<td>Exact syntax or policy</td>\n<td>A parser, schema, or deterministic validator</td>\n</tr>\n<tr>\n<td>Permission to do something</td>\n<td>An authorization system outside the model</td>\n</tr>\n</tbody>\n</table>\n<p>A prompt is the request. The adapter is the default. The validator is the law.</p>\n<p>Confusing those layers is how a style model becomes a source of truth, or a friendly answer becomes permission.</p>\n<h2 id=\"eight-megabytes-deserves-code-review\" style=\"position:relative;\"><a href=\"#eight-megabytes-deserves-code-review\" aria-label=\"eight megabytes deserves code review permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Eight megabytes deserves code review</h2>\n<p>The small file changes the operational shape of fine-tuning more than the cheap training does.</p>\n<p>I can keep one base model loaded and version the adapters separately. A commit-message adapter, an incident-summary adapter, maybe one trained on a project’s vocabulary. Rollback means selecting the previous directory, not replacing a 13 GB checkpoint. Experiments become cheap enough to branch.</p>\n<p>But “small” is not the same as “harmless.” An adapter can systematically change every answer while looking like a rounding error next to the base weights. I now want the same receipts around it that I want around infrastructure:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">commit-message-r4/\n├── adapter_config.json\n├── adapter_model.bin\n└── manifest.json</code></pre></div>\n<div class=\"gatsby-highlight\" data-language=\"json\"><pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n  <span class=\"token property\">\"base_model\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"facebook/opt-6.7b\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"base_revision\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"&lt;immutable commit>\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"dataset_sha256\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"&lt;hash>\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"split\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"chronological\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"train_on_prompt\"</span><span class=\"token operator\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"lora_rank\"</span><span class=\"token operator\">:</span> <span class=\"token number\">4</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"targets\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"q_proj\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"v_proj\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"evaluation\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"blind-held-out-commits-v1\"</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>The adapter and base revision are one deployable unit. Change either and the evaluation is stale. The dataset needs provenance because it is effectively executable behavior. Sample generations belong in the pull request. So do the embarrassing edge cases.</p>\n<p>And I would not casually stack adapters because the files are small. Two deltas added to the same matrices are not two independent browser extensions. “Writes like me” plus “classifies incidents” may compose beautifully or may produce incident reports that apologize for themselves. The only honest answer is another held-out set.</p>\n<p>Last month, <a href=\"/i-said-dont-give-the-model-hands-chatgpt-got-plugins/\">the text box learned verbs</a>. Plugins decide what the model <em>can</em> do. Fine-tuning changes what it <em>tends</em> to do. Capability needs permissions; tendency needs tests.</p>\n<p>Both need a rollback button.</p>\n<h2 id=\"fine-tuning-is-back-but-it-came-back-smaller\" style=\"position:relative;\"><a href=\"#fine-tuning-is-back-but-it-came-back-smaller\" aria-label=\"fine tuning is back but it came back smaller permalink\" class=\"anchor before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>Fine-tuning is back, but it came back smaller</h2>\n<p>In 2020, I thought fine-tuning meant making a personal copy of a model. In 2021, five examples in a prompt made that look wasteful. My revised revision is this:</p>\n<p>Prompting describes the task.</p>\n<p>Fine-tuning edits the defaults.</p>\n<p>The base model supplies broad capability. The adapter supplies a narrow gravitational field around the outputs you prefer. That field can encode taste, useful domain distinctions, and boring consistency. It can also encode every shortcut, imbalance, private string, and mislabeled example in the dataset.</p>\n<p>LoRA makes the experiment cheap enough for one person and the result small enough to version. That is genuinely exciting. It also means model behavior is becoming another artifact we can casually copy into production without reading, because there is no meaningful way to read two million floating-point numbers.</p>\n<p>So the review has to move outward: inspect the data, freeze the split, test the behavior, record the base revision, and keep deterministic boundaries around anything that matters.</p>\n<p>The model is 13 GB. My opinion of it is 8 MB.</p>\n<p>Eight megabytes is not much space.</p>\n<p>Apparently it is enough for a habit.</p>","timeToRead":10,"frontmatter":{"title":"The model is 13 GB. My opinion of it is 8 MB","date":"April 30, 2023","description":"I fine-tuned a 6.7B language model without changing the 6.7B parameters. LoRA turned behavior into a tiny patch — and made the dataset look uncomfortably like source code."}}},"pageContext":{"slug":"/the-model-is-13-gb-my-opinion-of-it-is-8-mb/","previous":{"fields":{"slug":"/how-a-pdf-button-became-remote-code-execution-and-paid-for-my-year/"},"frontmatter":{"title":"How a PDF button became remote code execution (and paid for my year)"}},"next":{"fields":{"slug":"/the-signature-was-valid-the-company-was-wrong/"},"frontmatter":{"title":"The signature was valid. The company was wrong"}}}},
    "staticQueryHashes": ["3037286347","63159454"]}