Skip to content

LearnTutorials

Fine-tune an LLM to match a writing style with Together AI

Fine-tune Qwen3.5 9B on your own text with a LoRA job on Together AI in about 40 minutes of hands-on work, then test its style against the base model.

By Recaply EditorsAdvanced40 min6 stepsUpdated

A hand holds up a tuning fork beside a fountain pen lying on a small stack of paper

What you need

Training runs on Together's servers after you start it, so add that wait to the hands-on time. Bigger files take longer.

  1. Step 1: Install the Together CLI and sign in

    4 min

    The tg command uploads data, starts jobs and deploys models, so every later step runs from one terminal.

    1. Run the command below to install the CLI with uv.
    2. Run tg login and approve the sign-in in your browser.
    3. Copy a key from the API keys page of your Together account, then run export TOGETHER_API_KEY=[YOUR KEY] for the test in step 6.
    Run this
    uv tool install "together[cli]" --upgrade

    You should see the sign-in approved in your browser, and no error in the terminal.

    The terminal says tg: command not found?

    uv's tool folder isn't on your PATH yet. Run uv tool update-shell, then open a new terminal window.

    Keep your API key out of scripts, notebooks and shared files. Set it in the terminal, as above, instead.

  2. Step 2: Turn your text into training examples

    15 min

    The model learns a style from pairs: a short request, then a passage from your text as the answer.

    1. Put your text in a folder named corpus, as .txt files.
    2. Delete what isn't prose, such as page numbers, tables of contents, indexes and running headers.
    3. Save the script below as make_data.py next to the folder, change [STYLE NAME], and run python3 make_data.py.
    Your script
    import json, pathlib
    
    STYLE = "[STYLE NAME]"
    examples = []
    for f in sorted(pathlib.Path("corpus").glob("*.txt")):
        paras = [p.strip() for p in f.read_text(encoding="utf-8").split("\n\n") if len(p.split()) > 20]
        chunk = []
        for p in paras:
            chunk.append(p)
            if sum(len(c.split()) for c in chunk) >= 300:
                text = "\n\n".join(chunk)
                topic = text.split(". ")[0][:150]
                examples.append({"messages": [
                    {"role": "user", "content": f"Write a passage in the style of {STYLE} about: {topic}"},
                    {"role": "assistant", "content": text}]})
                chunk = []
    
    with open("train.jsonl", "w", encoding="utf-8") as out:
        for e in examples:
            out.write(json.dumps(e, ensure_ascii=False) + "\n")
    print(len(examples), "examples written to train.jsonl")

    You should see a count of examples, and a train.jsonl file with one example per line.

    Only a few examples came out?

    The script splits paragraphs at blank lines and keeps those over 20 words. If your files break lines inside paragraphs, join those lines first.

    Together warns that training a reasoning model on examples without reasoning steps can weaken its reasoning. For a writing model, that matters less.

  3. Step 3: Check and upload the file

    3 min

    The check runs on your computer, so format errors show up before you pay for anything.

    1. Run the check below.
    2. Upload the file with tg files upload train.jsonl.
    3. Copy the file ID from the upload output.
    Run this
    tg files check train.jsonl

    You should see a check report with no errors, then a file ID after the upload.

    The check reports a format error?

    Each line must be one JSON object with a messages list. Don't mix formats: a file with both text and messages lines is rejected.

  4. Step 4: Start a LoRA fine-tune

    5 min

    LoRA trains a small add-on for the base model, which costs far less than retraining the whole model.

    1. Put your file ID into the command below and run it.
    2. Read the price estimate the CLI prints, then confirm to start the job.
    3. Copy the job ID, and follow progress with tg fine-tuning list-events [JOB_ID].
    Run this
    tg fine-tuning create --training-file [FILE_ID] --model Qwen/Qwen3.5-9B --lora --lora-r 16 --lora-alpha 32 --n-epochs 1 --suffix my-style

    You should see a price estimate, then a job ID once you confirm.

    The estimate is higher than you want?

    The cost is your training tokens times the epochs. Keep 1 epoch, or cut the file down, and run the command again.

    You can close the terminal while the job runs. tg fine-tuning retrieve [JOB_ID] shows its status at any time.

  5. Step 5: Deploy the tuned model

    5 min

    Tuned models run on a dedicated endpoint, not on Together's per-token API.

    1. When the job has finished, run tg fine-tuning retrieve [JOB_ID] and copy the model object name.
    2. Run the command below with that name.
    3. Run tg endpoints get [ENDPOINT_ID] until the state reads DEPLOYMENT_STATE_READY.
    4. Copy the endpoint string the deploy command printed, such as your-project/my-style.
    Run this
    tg endpoints deploy "[MODEL OBJECT NAME]" --endpoint my-style

    You should see the state DEPLOYMENT_STATE_READY, and an endpoint string to call.

    The deploy is refused?

    Endpoints need enough credit to start. Add credit to your Together account, then run the command again.

    The endpoint bills by the minute while it runs, even when idle.

  6. Step 6: Compare it with the base model, then shut down

    8 min

    The same prompts on both models show what the training changed.

    1. Send 3 prompts with the command below: a topic your text covers, a made-up topic, and a topic newer than your text.
    2. Run the same 3 prompts on the base model with ollama run qwen3.5:9b.
    3. When you're done, delete the endpoint with tg endpoints rm [ENDPOINT_ID] --force.
    Test it (Mac or Linux)
    curl https://api-inference.together.ai/v1/chat/completions -H "Authorization: Bearer $TOGETHER_API_KEY" -H "Content-Type: application/json" -d '{"model": "[YOUR ENDPOINT STRING]", "messages": [{"role": "user", "content": "Write a passage in the style of [STYLE NAME] about: [YOUR TOPIC]"}], "max_tokens": 600}'

    You should see the tuned model writing like your text, while the base model writes in its usual voice.

    The tuned model still sounds like the base model?

    Raise --lora-r and --lora-alpha, or add more examples, and train again. Together suggests a full fine-tune if that still falls short.

    Use the same 3 prompts after every new run, so you can tell whether a change helped.

You get

A Qwen3.5 9B model tuned on your text, tested side by side with the base model

Did this guide work for you?

Sources