Status: experimental v1, known issues (updated 2026-10-01). Not recommended as an agent. Tested in an agent harness, this adapter over-uses tools (a bare "hi" produced 17 to 22 parallel tool calls), ignores "do not use tools" instructions, sometimes answers with nothing, can run on without stopping, and ends turns with
[COT_END]instead of[REPLY_END].Measured cause. The delimiter tokens this chat format is built on (
[INST_START],[REPLY_END],[SYSTEM_*],[TOOL_RESULT_*], ...) are untrained placeholders in the base model: their embeddings are identical to the never-used<SPAN#5000>([REPLY_END],[TOOL_RESULT_*]bit-identical, the rest within 0.004). They are therefore indistinguishable to the model, and LoRA on attention cannot teach them, so the model cannot tell roles apart or emit[REPLY_END]. Secondary cause: outside the Tool-Use subsplit about 98% of training turns are tool calls. A v2 without placeholder tokens is in progress. The evaluation below predates this finding and only measures format.
A LoRA adapter (rank 16, alpha 16) for yandex/AliceAI-T5-35B-A0.6B, a 35B-total / 0.6B-active sparse-MoE encoder-decoder base model. The base model is a span-infilling (UL2) pretrain with no chat format. This adapter teaches it three things:
<tool_call>…</tool_call> when a tool is needed, read [TOOL_RESULT_*] turns, answer.[REPLY_END]</s>, never write the user's next turn, and
produce nothing (just </s>) when the conversation already ends on a finished assistant reply.| Adapter | kaivoss/AliceAI-T5-35B-A0.6B-chat-lora (this repo) |
| Merged bf16 weights | kaivoss/AliceAI-T5-35B-A0.6B-chat |
| Training data | kaivoss/UltraData-SFT-Agent-2609-GLM53-20k |
| Fine-tuning example | finetune_chat_lora.py in this repo |
The base repo ships no chat_template.jinja; chat_template.jinja here defines one. It
uses delimiter tokens that already exist in the vocabulary (no resize) inside the model's UL2 frame:
encoder: [_S_][SYSTEM_START]…[SYSTEM_END][INST_START]…[INST_END][REPLY_START]…[REPLY_END][TOOL_RESULT_START]…[TOOL_RESULT_END]…[REPLY_START]<SPAN#0>
decoder: <s><SPAN#0> → [COT_START]reasoning[COT_END] text\n<tool_call>name<arg_key>k</arg_key><arg_value>v</arg_value></tool_call>[REPLY_END]</s>
Tools go in the system message as text, in the format GLM-5.3-Flash's own template renders
(# Tools … <tools>{json per line}</tools> … <tool_call>{function-name}<arg_key>…). The training data already
has this block; see the dataset card. Reasoning is rendered only for the turn being predicted and dropped from history.
import torch
from huggingface_hub import hf_hub_download
from peft import PeftModel
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
hf_hub_download("kaivoss/AliceAI-T5-35B-A0.6B-chat-lora", "alice_chat.py", local_dir=".")
from alice_chat import clean_decode, parse_tool_calls
BASE, ADAPTER = "yandex/AliceAI-T5-35B-A0.6B", "kaivoss/AliceAI-T5-35B-A0.6B-chat-lora"
tok = AutoTokenizer.from_pretrained(ADAPTER, trust_remote_code=True) # carries the chat template
model = AutoModelForSeq2SeqLM.from_pretrained(BASE, trust_remote_code=True, dtype=torch.bfloat16,
attn_implementation="eager", device_map={"": "cuda:0"})
model = PeftModel.from_pretrained(model, ADAPTER).eval() # or load the merged repo directly
messages = [{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Give me one fact about Zürich."}]
prompt = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
src = tok(prompt, add_special_tokens=False, return_tensors="pt").input_ids.to(model.device)
span = tok.convert_tokens_to_ids("<SPAN#0>")
dec = torch.tensor([[model.config.decoder.bos_token_id, span]], device=model.device)
with torch.inference_mode():
out = model.generate(input_ids=src, attention_mask=torch.ones_like(src), decoder_input_ids=dec,
do_sample=False, max_new_tokens=512)
gen = out[0, 2:].tolist()
gen = gen[: gen.index(tok.eos_token_id)] if tok.eos_token_id in gen else gen
text = clean_decode(tok, gen) # see "Tokenizer quirk" below; empty string = "nothing to say"
print(text, parse_tool_calls(text))
finetune_chat_lora.py is the script that produced this adapter. It is the model repo's
finetune_example.py with pre-tokenised 32k-context examples, r=16/α=16, an attention-backend fallback list
and peak-memory logging. prepare_data.py builds the examples, eval_chat.py
the base-vs-LoRA comparison, run.sh the exact pod pipeline.
pip install -r requirements.txt
python prepare_data.py --out data # one example per assistant turn
python finetune_chat_lora.py --data data/train.jsonl --out adapter # 200 steps, one 141 GB GPU
Each example keeps the repo's layout: input_ids=[[_S_] … <SPAN#0>], decoder_input_ids=[<s>, <SPAN#0>, *target],
labels=[-100, *target, </s>]. The loss is on the target only, and the target always ends in </s>.
| Method | LoRA (bf16 base, not QLoRA, not full fine-tuning) |
| Rank / alpha / dropout | 16 / 16 / 0.05 (scale 1.0) |
| Targets | q_proj, k_proj, v_proj, o_proj in encoder self-attn, decoder self-attn and decoder cross-attn |
| Steps | 200 (batch 1 × 8 accumulation → 1,600 examples, each seen once) |
| Optimiser | AdamW, lr 2e-4, cosine, 10 warmup steps, bf16, gradient checkpointing |
| Context | up to 32,768 tokens (source + target); longer examples are dropped, not truncated |
| Hardware | 1 × H200 SXM (141 GB) |
The experts are fused nn.Parameters rather than nn.Linear, so LoRA cannot wrap them: only attention is adapted.
One example per assistant turn: the encoder reads everything before it, the decoder learns that turn. About 10% are
"stay silent" examples whose source already ends on a finished assistant reply and whose target is just </s>.
Equal numbers come from each upstream subsplit.
| subsplit | kept | silent rows | mean tokens | max tokens | rejected while sampling |
|---|---|---|---|---|---|
Code-Agent |
525 | 53 | 18,397 | 32,662 | {'source_too_long': 181} |
General-Agent |
525 | 51 | 25,725 | 32,745 | {'source_too_long': 131, 'target_too_long': 10} |
Search-Agent |
525 | 53 | 11,786 | 32,687 | {'source_too_long': 37} |
Tool-Use |
525 | 53 | 7,308 | 26,120 | {'source_too_long': 1} |
Held-out examples from the same distribution, greedy decoding, base model and adapter run through the same prompts.
| metric | base | LoRA |
|---|---|---|
clean stop (ends on </s>) |
5.0% | 22.5% |
| wrote a user/system/tool turn | 0.0% | 0.0% |
| silent when nothing to answer | 0.0% | 100.0% |
| empty when it should answer | 0.0% | 2.5% |
| tool call made when gold has one | 68.8% | 68.8% |
| tool calls well-formed | 92.9% | 100.0% |
| first tool name matches gold | 59.1% | 40.9% |
| tool call when gold has none | 75.0% | 0.0% |
n = 40 held-out examples, greedy decoding.
Read these numbers with care. The eval is small (n = 40, only 2 non-silent Tool-Use examples) and generation is capped at
384 new tokens. Code, General and Search turns usually open with a long [COT_START] reasoning block, so a capped output is
indistinguishable from a runaway one: clean stop and tool-call recall are lower bounds, not stop/recall rates. What the
per-row data does support:
| base | LoRA | |
|---|---|---|
| "Stay silent" rows answered with nothing (6 rows) | 0 / 6 | 6 / 6 |
| Tool call written on a silent row | 5 / 6 | 0 / 6 |
| Turns that should call a tool and did (Code-Agent, 10) | 6 | 8 |
| ... General-Agent (11) | 6 | 9 |
| ... Search-Agent (11) | 10 | 5 |
| Well-formed tool calls | 92.9% | 100% |
| First tool name matches gold | 59.1% | 40.9% |
| User/system/tool turn written by the model | 0% | 0% |
</s>.Training loss (mean of 8-example steps, so noisy): about 1.3 at the start, mean 0.84 over the 200 steps, 0.5-1.1 by the end.
The raw per-example outputs are in eval_results.json; a proper eval needs a larger sample, a larger
token cap, and execution of the tool calls.
Metaspace(prepend_scheme="always") with \n as its own token, so tok.decode
inserts a space after every newline and delimiter. Use clean_decode from alice_chat.py, especially for
code in tool arguments. A line with exactly one leading space is indistinguishable from one with none at the token level.finetune_chat_lora.py sets
_supports_sdpa itself (checked numerically against eager, including padded batches, on a tiny model). The inference snippet
above uses eager; at 32k prompts use the same SDPA patch or flash-attn.Apache-2.0, matching the base model (yandex/AliceAI-T5-35B-A0.6B, © Yandex LLC) and the data (openbmb/UltraData-SFT-Agent-2609, arXiv:2602.09003).
Have the files? Submit a torrent.
Submit a torrentHave the complete files and permission to share them? Replace the example folder path. The script checks the files, makes a torrent and submits its magnet and checksums. Keep your torrent client seeding. Get your community key from your settings, and read the script before running it.
curl -fsSL https://pirateface.co/package.sh | bash -s -- --repo kaivoss/AliceAI-T5-35B-A0.6B-chat-lora ./model-folderyandex/AliceAI-T5-35B-A0.6B