Status: v4, fixes the failure it targeted but is not yet robust. After a tool result under
pi's prompt it now answers instead of repeating the call (after-tool probes 22/23; v3: 8/23 on the same probes). It passes 90% of the 103 probes greedy (v3: 80%) but only 80% sampled at T=0.6, a gap that suggests its decisions are not yet confident. Regressions against v3: a plain general question underpi's prompt sometimes triggers a pointless file read (2/4 greedy), "don't use tools" is ignored in 3 of 8 negation probes, and on held-out real turns the right-kind-of-reply rate fell from 91% to 83% (7 of 38 tool-call turns were answered in text instead). It missed the bar set before the run (sampled probes ≥ 90%, held-out decision accuracy ≥ 88%, no turn-marker leaks), so it was not scaled up.
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 with no chat format. It teaches the model to chat, to call tools when a task needs them, to answer in text when it does not, and to say nothing when there is nothing to say.
| Adapter | kaivoss/AliceAI-T5-35B-A0.6B-chat-lora-v4 (this repo) |
| Merged bf16 weights | kaivoss/AliceAI-T5-35B-A0.6B-chat-v4 |
| Fine-tuning example | finetune_chat_lora.py in this repo |
| Earlier versions | v3 (88% of its probes; repeated calls after tool results), v2, v1 (superseded, known flaw) |
v1 ran inside an agent harness and misbehaved: a bare "hi" produced 17 to 22 parallel tool calls, "don't read any skills"
was ignored, replies were empty or endless. Cause, measured: its chat format used the vocabulary's [INST_START],
[REPLY_END], [SYSTEM_*], [TOOL_RESULT_*] tokens, whose embeddings in the base model are untrained placeholders,
identical to the unused <SPAN#5000>. The model could not tell roles apart or emit [REPLY_END], and LoRA on attention cannot
teach embeddings. placeholder_ids.json lists all 5,832 placeholder tokens, and a unit test fails if
the chat format ever uses one.
v2 switched to plain-text role markers and </s> as the end of a turn, and mixed plain chat into the data. It passed 61%
of its behaviour probes (base model: 11%) but still, under a coding-agent prompt, invented a task for a bare "hi" and called a
tool, repeated a tool call after its result instead of answering, over-thought simple questions, and answered "thanks" with
reasoning instead of silence.
v3 changed only the data: more greetings, small talk and "no tools" instructions under varied agent prompts, real "final answer
right after a tool result" turns, reasoning capped at 600 tokens and optional, thank-you silence under harness prompts, and no
training target that repeats a recent call. It passed 88% of the (rebuilt) probes, with greetings at 24/24, but under pi's prompt
it still repeated the call it had just made instead of answering once a tool result arrived (after-tool probes 4/10). The cause
was visible in the data: pi's prompt only ever appeared with plain replies, never next to a tool result.
v4 changes only the data again, to put exactly that into training:
read, exec, edit, write) are nearly pi's (read, bash,
edit, write). About 85% of its calls map, but no whole trajectory does (each ends up calling something unmappable), so only the
part of a trajectory before its first unmappable call is used: tool names mapped, {"result": ...} results unwrapped, edit
arguments parsed, and pi's system prompt and tool schemas swapped in.pi flows (pi_flows.py): read a value, a list, a note or code; ls; cat; wc -l; write; a
two-step edit (read, then an edit that differs from the read); compare two files. Results and final answers are deterministic.pi task.The base repo ships no chat template; chat_template.jinja defines one inside the model's UL2 frame:
encoder: [_S_]<|system|>…\n<|user|>…\n<|assistant|>…\n<|tool|>…\n<|assistant|><SPAN#0>
decoder: <s><SPAN#0> → <think>reasoning</think>text\n<tool_call>name<arg_key>k</arg_key><arg_value>v</arg_value></tool_call></s>
Tools go in the system message as text in the GLM format (# Tools … <tools>{json per line}</tools> …), produced by
glm_tools.py. Reasoning is optional and short. An empty reply is just </s>. Pass alice_frame=False to
apply_chat_template when a runtime adds [_S_] and <SPAN#0> itself (vLLM plugin below).
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-v4", "alice_chat.py", local_dir=".")
from alice_chat import clean_decode, parse_output
BASE, ADAPTER = "yandex/AliceAI-T5-35B-A0.6B", "kaivoss/AliceAI-T5-35B-A0.6B-chat-lora-v4"
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)
out = model.generate(input_ids=src, attention_mask=torch.ones_like(src), decoder_input_ids=dec,
do_sample=True, temperature=0.6, top_p=0.95, 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
reasoning, content, tool_calls = parse_output(clean_decode(tok, gen))
Use sampling, not greedy: greedy decoding looped in testing.
The community plugin (AntonProkopyev/AliceAI-T5-35B-A0.6B-vLLM)
loads the merged repo unchanged, but it only serves /v1/completions and adds the UL2 frame itself.
alice_openai_shim.py sits in front of it and provides /v1/chat/completions with tools,
reasoning and streaming (simulated). It also samples by default (the harness sends no temperature), merges duplicate tool
calls, caps parallel calls, never lets a turn continue as the user, and retries an empty reply after a user or tool message.
serve_smoke.py exercises it with the openai Python client.
| 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 (experts are fused parameters, not nn.Linear) |
| Steps | 200 (batch 1 × 8 accumulation, each example seen once) |
| Optimiser | AdamW, lr 2e-4, cosine, 10 warmup steps, bf16, gradient checkpointing |
| Context | up to 32,768 tokens; longer examples are skipped per turn, never truncated, and turn choice is fit-aware so no row is lost |
| Attention | SDPA (the remote code does not declare it; enable_sdpa() sets the flag, checked against eager on padded batches) |
| Hardware | 1 × H200 |
One example per assistant turn: the encoder reads everything before it, the decoder learns that turn, then </s>.
| category | examples | mean tokens | max tokens | reply kinds |
|---|---|---|---|---|
agent_action |
450 | 13,626 | 31,187 | {'action': 450} |
agent_final |
200 | 11,553 | 27,417 | {'final': 200} |
agent_plain |
140 | 6,223 | 17,996 | {'plain': 140} |
chat_plain |
160 | 371 | 2,485 | {'plain': 160} |
chat_tools |
120 | 4,300 | 7,572 | {'plain': 120} |
eval_agent |
100 | 15,165 | 29,889 | {'action': 83, 'plain': 17} |
eval_chat |
40 | 651 | 1,924 | {'plain': 40} |
harness |
130 | 2,638 | 6,101 | {'plain': 130} |
pi_flows |
350 | 1,523 | 2,017 | {'final': 211, 'action': 139} |
pi_retarget |
350 | 7,381 | 26,087 | {'action': 350} |
silent_agent |
40 | 11,631 | 26,269 | {'silent': 40} |
silent_synth |
60 | 1,476 | 1,983 | {'silent': 60} |
Sources: real agent trajectories (UltraData-SFT-Agent-2609,
GLM-formatted copy linked above), plain Q&A from UltraData-SFT-2605
(English IF and Knowledge), greetings and system-prompted chats from
smoltalk, a sanitized capture of the pi coding-agent prompt and tool
schemas (harness_pi.json), General-Agent trajectories retargeted onto pi's tools, templated pi flows
(pi_flows.py), and templated greetings, small talk, "no tools" instructions and thank-yous (phrases.py). Targets are filtered to at most 8 parallel calls, 600 reasoning tokens and no repeat of a recent call.
Held-out rows are never trained on.
Behaviour probes (probes.py): greetings, "don't use tools", small talk and plain questions under pi-style
prompts (with and without a 14-skill list, and with a tool subset) and a non-coding weather / calculator / search assistant;
real first calls; answering after a tool result; the second call of a multi-step task; silence after a thank-you or a finished task. A probe passes only if every expectation holds, the reply
finished, and it never wrote another turn. The previous adapter (v3) is scored on the same probes.
| probe category | this adapter, greedy | this adapter, sampled | previous adapter (v3), greedy |
|---|---|---|---|
| action | 16/18 | 13/18 | 14/18 |
| after_tool | 22/23 | 22/23 | 8/23 |
| greeting | 24/24 | 20/24 | 24/24 |
| mid_step | 2/3 | 1/3 | 3/3 |
| neg_question | 4/4 | 4/4 | 4/4 |
| negation | 5/8 | 6/8 | 7/8 |
| plain_question | 2/4 | 1/4 | 4/4 |
| silent | 12/13 | 10/13 | 12/13 |
| smalltalk | 6/6 | 5/6 | 6/6 |
| all probes passed | 90% | 80% | 80% |
Held-out real turns (this adapter):
n = 60 held-out rows, greedy, 1,024 new tokens.
| metric | adapter |
|---|---|
| generations cut off at the token cap (not judged below) | 3.3% |
| right kind of reply (tool call / text / nothing) | 82.8% |
| ... when the right reply was a tool call | 81.6% |
| ... when the right reply was text | 85.0% |
| ... when the right reply was nothing | n/a |
| first tool name matches the real one | 67.7% |
| wrote another turn (`< | user |
| turns with duplicate tool calls | 0.0% |
| most tool calls in one turn | 7 |
These are small samples (103 probes, a few dozen held-out rows) of greedy or single-sample generations. They check for the specific failures above; they are not a capability benchmark. Tool-call arguments are not checked, only the tool name, and the tool-name metric counts a reasonable alternative as wrong.
pi flows are templated, which is why a hash-held-out 20% of their
wording is kept for the probes; real-world phrasing outside those templates is untested.clean_decode from
alice_chat.py (important for code in tool arguments).bash commands if a harness lets it.Apache-2.0, matching the base model (© Yandex LLC) and the data (UltraData: arXiv:2602.09003; smoltalk: Apache-2.0 for its new subsets).
yandex/AliceAI-T5-35B-A0.6BHave 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-v4 ./model-folder