| """Convert our `{"messages": [...]}` chat rows into the (input, output) pair |
| the Megatron-Bridge SFT builder expects. |
| |
| The builder combines them as `"{input} {output}"` + EOS and masks the loss to |
| the `output` span (sft.py: prompt_template, add_eos=True). So: |
| |
| input = the conversation up to (but not including) the final assistant turn, |
| rendered through the model's own chat template with a generation |
| prompt appended -> exactly what the game feeds the model at play time. |
| output = the final assistant turn's content (the Warden's line, or a tool-call |
| JSON string for director examples). |
| |
| Rendering through the real chat template keeps train == play: the live game |
| prompts the GGUF with the same Jinja template via llama.cpp. |
| """ |
| from typing import Any, Optional |
|
|
| from megatron.bridge.data.builders.hf_dataset import ProcessExampleOutput |
| from megatron.bridge.training.tokenizers.tokenizer import MegatronTokenizer |
|
|
|
|
| def _hf_tokenizer(tokenizer): |
| """Reach the underlying HF tokenizer that owns apply_chat_template.""" |
| for attr in ("apply_chat_template",): |
| if hasattr(tokenizer, attr): |
| return tokenizer |
| for attr in ("_tokenizer", "tokenizer", "hf_tokenizer"): |
| inner = getattr(tokenizer, attr, None) |
| if inner is not None and hasattr(inner, "apply_chat_template"): |
| return inner |
| raise RuntimeError("could not find an apply_chat_template-capable tokenizer") |
|
|
|
|
| def process_warden_example( |
| example: dict[str, Any], tokenizer: Optional[MegatronTokenizer] = None |
| ) -> ProcessExampleOutput: |
| messages = example["messages"] |
| if not messages or messages[-1]["role"] != "assistant": |
| raise ValueError("every training row must end with an assistant turn") |
|
|
| prompt_messages = messages[:-1] |
| target = messages[-1]["content"] |
|
|
| tok = _hf_tokenizer(tokenizer) |
| _input = tok.apply_chat_template( |
| prompt_messages, |
| tokenize=False, |
| add_generation_prompt=True, |
| ) |
| |
| |
| _input = _input.rstrip(" ") |
|
|
| return ProcessExampleOutput(input=_input, output=target, original_answers=[target]) |
|
|