Hugging Face TITO, 에이전트 RL의 토큰 드리프트 경고
Hugging Face가 tool-using agent RL에서 재토큰화가 gradient를 깨뜨리는 문제와 TITO 해결책을 공개했습니다.
- 무슨 일: Hugging Face authors가 2026년 5월 29일 agentic RL용 TITO 원칙을 공개했습니다.
- TITO는 Token-In, Token-Out의 약자로, sampled token을 decode 후 다시 encode하지 말라는 규칙입니다.
- 문제: tool call rollout을 message list로 재렌더링하면 model이 실제 생성하지 않은 token에 gradient가 걸릴 수 있습니다.
- 해결: sampled token buffer를 source of truth로 두고, tool response만 prefix-preserving delta token으로 append합니다.
- 글은 open-weight family 19개 중 18개가 tool message prefix preservation을 만족한다고 보고합니다.
- 주의점: compaction, history rewriting, sub-agent summary는 sampled trajectory를 바꾸므로 loss mask 설계가 필요합니다.
Hugging Face가 2026년 5월 29일 Agentic RL: Token-In, Token-Out Done Right를 공개했습니다. 글의 저자는 Quentin Gallouédec와 Kashif Rasul입니다. 주제는 제품 발표보다 낮은 층에 있습니다. tool을 쓰는 LLM agent를 reinforcement learning으로 학습할 때, 모델이 실제로 sampling한 token이 아니라 decode 후 다시 렌더링한 token에 gradient를 걸면 학습 신호가 조용히 깨질 수 있다는 문제입니다.
이 글이 뉴스인 이유는 에이전트 RL이 보상 함수나 benchmark만의 문제가 아니라는 점을 짚었기 때문입니다. agent rollout은 더 이상 assistant가 한 번 답하고 끝나는 completion이 아닙니다. 모델이 tool call을 생성하고, 외부 시스템이 tool을 실행하고, tool result가 conversation에 붙고, 모델이 다시 이어서 생성합니다. 이 과정에서 token boundary, tool result loss mask, chat template rendering이 모두 학습 objective의 일부가 됩니다.
Hugging Face 글의 원칙은 짧습니다. RL은 model이 생성한 정확한 token에 대해서만 update해야 합니다. tool response는 policy가 생성한 token이 아니므로 loss를 걸면 안 됩니다. single-turn completion에서는 이 말이 거의 정의처럼 보입니다. multi-turn agent rollout에서는 구현이 이 원칙을 쉽게 어깁니다. 특히 assistant output을 decode해 tool call을 parse하고, message list에 tool result를 append한 뒤, 전체 conversation을 다시 chat template으로 render/tokenize하는 loop가 위험합니다.

문제의 첫 번째 원인은 tokenization이 완전한 역함수가 아니라는 점입니다. 서로 다른 token sequence가 같은 문자열로 decode될 수 있고, 그 문자열을 다시 encode하면 tokenizer의 greedy segmentation이 다른 token IDs를 만들 수 있습니다. 여기에 JSON whitespace, argument ordering, boolean casing, special token rendering 차이가 붙습니다. 결과 문자열은 같아 보여도 token ID sequence는 model이 sampling한 것과 달라질 수 있습니다.
두 번째 원인은 loss mask입니다. 자연스러운 구현은 conversation을 message list로 유지하다가 마지막에 전체를 tokenize하고, assistant turn과 tool turn을 다시 찾아 loss mask를 복원합니다. Hugging Face는 이 방식을 MITO라고 부릅니다. 이 loop에서는 per-turn boundary를 잃은 뒤 다시 복구해야 하고, chat template마다 role marker와 assistant region을 찾아야 합니다. 구현 난도가 올라가는 정도가 아니라, 잘못 복구하면 tool response token에도 loss가 걸립니다.
TITO는 이 두 문제를 한 번에 피합니다. sampled token을 running buffer에 바로 append하고, 이 buffer를 source of truth로 둡니다. 모델이 낸 token은 decode해 tool call인지 확인할 수 있지만, decoded dict는 routing에만 씁니다. tool을 실행할지 결정한 뒤에는 decoded text를 다시 encode하지 않습니다. buffer에는 model이 실제 sampling한 IDs가 그대로 남고, loss mask도 append 시점에 함께 기록됩니다.
# 개념 예시입니다. 핵심은 decoded content를 다시 encode하지 않는 것입니다.
buffer.extend(sampled_ids)
loss_mask.extend([1] * len(sampled_ids))
tool_call = parse_for_routing_only(sampled_ids)
if tool_call:
tool_result_delta = compute_tool_response_delta(tokenizer, tool_call.result)
buffer.extend(tool_result_delta)
loss_mask.extend([0] * len(tool_result_delta))
남는 문제는 tool response를 어떤 token으로 붙이느냐입니다. 모델은 보통 <tool_response>...</tool_response>나 role marker가 포함된 다음 assistant prompt를 기대합니다. TITO는 여기서 전체 conversation을 다시 렌더링하지 않습니다. 대신 tool message가 있는 dummy render와 없는 dummy render를 만들고, prefix를 뺀 suffix delta만 가져옵니다. Hugging Face 글의 Qwen2.5 예시는 이 delta가 <|im_start|>user\n<tool_response>\n4\n</tool_response><|im_end|>\n<|im_start|>assistant\n에 해당하는 token sequence라고 보여줍니다.
이 방식에는 조건이 하나 있습니다. chat template이 tool message에 대해 prefix-preserving이어야 합니다. 즉 assistant tool call 뒤에 tool result를 append했을 때, 새 render의 앞부분이 이전 render를 token-for-token 그대로 포함해야 합니다. Hugging Face는 이를 property test로 확인할 수 있다고 말합니다. dummy user message, assistant tool call, tool message를 만들고, 두 render의 prefix가 같은지 비교하면 됩니다.
글에서 가장 구체적인 수치는 19개 중 18개입니다. Hugging Face authors는 Qwen2.5, Qwen2.5-Coder, Qwen3 계열, DeepSeek, Llama, Gemma, gpt-oss, GLM, MiniMax-M2.1 등에서 tool message prefix preservation을 확인했다고 적었습니다. 예외는 Qwen3였습니다.
Qwen3의 실패도 흥미로운 구현 사례입니다. 글에 따르면 Qwen3 template은 assistant turn이 마지막일 때 빈 <think>...</think> block을 tool call 앞에 넣습니다. 그런데 tool result가 append되면 그 assistant turn은 더 이상 마지막이 아니어서 block이 사라집니다. 이 순간 prefix가 깨집니다. Hugging Face는 Jinja conditional을 {%- if true %}로 바꾸는 한 줄 수정으로 prefix preservation을 복구할 수 있다고 설명합니다. inference 비용은 바뀌지 않는다고도 적었습니다.
이 사례는 에이전트 RL의 실패가 거대한 이론 문제가 아니라 작은 template 조건문에서 시작될 수 있음을 보여줍니다. model family가 tool call을 지원한다고 해서 학습 loop가 자동으로 맞아지는 것은 아닙니다. chat template은 inference UX의 포맷 파일처럼 보이지만, RL에서는 gradient가 어떤 token에 걸리는지 결정하는 회로가 됩니다. Qwen3의 한 줄 수정은 그 경계를 잘 보여줍니다.
Hugging Face 글은 renderer 접근도 비교합니다. renderers library는 Qwen3, GLM, DeepSeek-V3, Kimi, gpt-oss 등 model family별 renderer를 제공하고, messages-to-tokens boundary를 Python object로 관리합니다. 이 방식은 message_indices와 loss_mask를 제공하고, unsafe bridge를 loud하게 실패시키는 장점이 있습니다. vendor API처럼 token stream을 직접 쥘 수 없고 message만 다룰 수 있는 환경에서는 renderer가 필요할 수 있습니다.
TITO의 주장은 renderer를 모두 버리자는 쪽이 아닙니다. token을 직접 보유하는 RL pipeline에서는 sampled buffer가 이미 drift를 막기 때문에, family별 renderer의 유지보수 부담을 줄일 수 있다는 쪽입니다. 새 model이 나올 때마다 renderer를 손으로 고치는 대신, prefix-preserving property test를 돌리고 tool response delta만 계산하면 됩니다. 이 차이는 agentic RL platform을 여러 open-weight model에 붙이는 팀에게 직접적인 운영 비용입니다.
edge case도 숨기지 않습니다. 가장 큰 예외는 history rewriting입니다. long-running coding agent는 context limit에 가까워지면 conversation을 compact합니다. Claude Code, aider, Codex 같은 coding agent는 과거 turn을 summary로 바꾸기도 합니다. sub-agent setup에서는 child agent의 긴 trace가 parent에게 summary로만 돌아옵니다. 이런 순간에는 이전에 sampling된 token이 다음 prompt에 그대로 들어가지 않습니다.
Hugging Face는 이 경우 objective 자체가 달라진다고 말합니다. PPO나 GRPO의 importance ratio는 policy가 생성하지 않은 Frankenstein prompt에 대해 말할 수 없습니다. 제안된 workaround는 마지막 rewrite 지점 이전을 prompt로 freeze하고 loss mask를 0으로 두는 방식입니다. 그 뒤에 이어진 genuine sampled token만 gradient를 갖습니다. 대가도 명확합니다. rewrite가 rollout 뒤쪽에서 자주 일어나면 긴 trajectory 중 마지막 몇백 token만 학습 신호로 남을 수 있습니다.
truncation은 상대적으로 단순합니다. rollout이 max_seq_len에서 mid-turn으로 잘리면 renderer는 close token이 없어 safe extension을 증명하지 못할 수 있습니다. TITO에서는 buffer가 generation된 곳에서 끝나고, loss mask는 그대로 유지됩니다. reasoning block이 닫히지 않았거나 tool call이 incomplete하면 parser는 tool을 dispatch하지 않습니다. 남은 budget이 없으므로 tool result를 붙일 필요도 없습니다.
커뮤니티 반응은 아직 작습니다. Hugging Face 페이지는 확인 시점에 upvote 2개였고, 댓글은 "saving this for later" 한 줄 수준입니다. 그러나 반응 규모가 작다고 해서 실무 가치가 작다는 뜻은 아닙니다. agentic RL을 직접 구현하는 팀은 많지 않고, 이 문제는 학습 curve가 이상하게 흔들리거나 shape mismatch가 난 뒤에야 눈에 들어옵니다. 글은 그 실패를 token accounting 문제로 좁혀 보여줍니다.
AI 개발팀이 가져갈 체크리스트는 구체적입니다. 첫째, tool-using rollout에서 sampled IDs를 buffer로 보존하는지 확인해야 합니다. 둘째, assistant token과 tool token의 loss mask가 append 시점에 만들어지는지 봐야 합니다. 셋째, chat template이 tool message append에 대해 prefix-preserving인지 property test를 넣어야 합니다. 넷째, compaction이나 sub-agent summary처럼 과거 token을 rewrite하는 기능은 RL objective와 별도 정책으로 다뤄야 합니다.
이 글은 모델 성능 발표가 아닙니다. 새 benchmark 1위도 아니고, 더 큰 parameter 수도 아닙니다. 대신 agent training loop의 아주 낮은 층을 건드립니다. tool을 쓰는 agent가 늘수록 "모델이 무엇을 생성했는가"와 "학습 코드가 무엇을 token으로 보았는가" 사이의 차이가 커집니다. Hugging Face TITO는 그 차이를 줄이는 규칙입니다. agentic RL을 제품화하려는 팀에게는 reward model만큼 token provenance도 운영 지표가 됩니다.