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.

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.
Step 1: Install the Together CLI and sign in
4 minThe
tgcommand uploads data, starts jobs and deploys models, so every later step runs from one terminal.- Run the command below to install the CLI with uv.
- Run
tg loginand approve the sign-in in your browser. - 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 thisuv 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.
Step 2: Turn your text into training examples
15 minThe model learns a style from pairs: a short request, then a passage from your text as the answer.
- Put your text in a folder named
corpus, as.txtfiles. - Delete what isn't prose, such as page numbers, tables of contents, indexes and running headers.
- Save the script below as
make_data.pynext to the folder, change [STYLE NAME], and runpython3 make_data.py.
Your scriptimport 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.jsonlfile 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.
- Put your text in a folder named
Step 3: Check and upload the file
3 minThe check runs on your computer, so format errors show up before you pay for anything.
- Run the check below.
- Upload the file with
tg files upload train.jsonl. - Copy the file ID from the upload output.
Run thistg 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
messageslist. Don't mix formats: a file with bothtextandmessageslines is rejected.Step 4: Start a LoRA fine-tune
5 minLoRA trains a small add-on for the base model, which costs far less than retraining the whole model.
- Put your file ID into the command below and run it.
- Read the price estimate the CLI prints, then confirm to start the job.
- Copy the job ID, and follow progress with
tg fine-tuning list-events [JOB_ID].
Run thistg 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.Step 5: Deploy the tuned model
5 minTuned models run on a dedicated endpoint, not on Together's per-token API.
- When the job has finished, run
tg fine-tuning retrieve [JOB_ID]and copy the model object name. - Run the command below with that name.
- Run
tg endpoints get [ENDPOINT_ID]until the state readsDEPLOYMENT_STATE_READY. - Copy the endpoint string the deploy command printed, such as
your-project/my-style.
Run thistg 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.
- When the job has finished, run
Step 6: Compare it with the base model, then shut down
8 minThe same prompts on both models show what the training changed.
- Send 3 prompts with the command below: a topic your text covers, a made-up topic, and a topic newer than your text.
- Run the same 3 prompts on the base model with
ollama run qwen3.5:9b. - 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-rand--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
Try next
Did this guide work for you?
Thanks. It helps us fix the steps.


