feat: add MultiturnJSON datamodule + full multi-turn SFT support - #2317
Open
ysjprojects wants to merge 2 commits into
Open
feat: add MultiturnJSON datamodule + full multi-turn SFT support#2317ysjprojects wants to merge 2 commits into
ysjprojects wants to merge 2 commits into
Conversation
ysjprojects
requested review from
andyland,
k223kim,
lianakoleva and
t-vi
as code owners
August 31, 2026 00:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
MultiturnJSON, theDataModulethat wiresMultiturnSFTDataset(#<PR for sj/multiturnsftdataset>)up to
litgpt finetune, plus a fix to a downstream assumption that would otherwisecrash any real training run using it. Together these make multi-turn JSON finetuning
work end-to-end:
litgpt finetune lora --data MultiturnJSON --data.json_path ... --data.prompt_style llama3 ....Stacked on #2315, which itself stacks on #2314
What's included
MultiturnJSONDataModule (litgpt/data/multiturn_json_data.py)train.json/val.jsondirectory, same file/dir/split-loadingbehavior as
JSON(litgpt/data/json_data.py) — reuses its pattern rather than duplicating it.prompt_styleis required, no default (unlikeJSON'sprompt_style="alpaca") — there'sno single safe default chat template across checkpoints, and silently applying the wrong one is
a real correctness risk.
prompt_style.supports_multiturnat construction, raising immediately with a clearmessage instead of failing deep inside a
DataLoaderworker.mask_promptdefaults toTrue(vs.JSON'sFalse) — masking is the whole point ofmulti-turn SFT: only assistant turns should contribute to the loss.
to_messages()):{"messages": [{"role": "system"|"user"|"assistant", "content": str}]}{"conversations": [{"from": "system"|"human"|"gpt", "value": str}]}(role-mapped via
sharegpt_to_messages():human→user,gpt→assistant)ValueErrornaming the offending example's keys.litgpt.dataalongsideJSON/MultiturnSFTDataset.Fix:
select_sft_generate_examplecrashed on multi-turn data (litgpt/utils.py)This function powers the periodic and end-of-training "generate a sample" step in every
finetune script (
litgpt/finetune/lora.pyetc.) — it runs unconditionally at the end ofevery training run and periodically every
eval.intervalsteps, so it's not something a configflag can route around. It assumed every dataset example has an
"instruction"key, which isn'ttrue for multi-turn examples (
{"messages": [...]}/{"conversations": [...]}"), so any realMultiturnJSON-backed finetune run wouldKeyErrormid-training.Added
_instruction_of(example, transform): applies the dataset's owntransformfirst (reusingthe same mechanism
MultiturnSFTDataset/SFTDatasetalready carry, rather than hardcodingschema knowledge into
utils.py), then returnsexample["instruction"]for single-turn examplesor the last user turn's content for multi-turn ones. This is fully backward compatible — for
every existing single-turn
DataModule(with or without a transform), behavior is unchanged.This is now ready for real multi-turn finetuning end-to-end
Verified beyond unit tests — actually ran the full path with a real (non-mocked)
MultiturnJSONinstance:
DataModule.setup()→MultiturnSFTDataset→DataLoaderbatching → collate →select_sft_generate_example(), for both OpenAI-style and ShareGPT-style records, mixed in thesame file.
litgpt finetune lora --data MultiturnJSON ...should now run start to finish withoutcrashing at the sample-generation step.
Tests
tests/data/test_multiturn_json.py(new, mirrorstests/data/test_json.py's structure):file/JSONL loading, directory splits, path/warning validation, the
supports_multiturnguard,ShareGPT format, mixed-format files, and direct unit tests of
to_messages/sharegpt_to_messages.tests/test_utils.py::test_select_sft_generate_example— updated so its mocks accuratelyreflect a real dataset's default
transform=None(this test regressed against the fix untilfixed, since
MagicMock().transformauto-vends a truthy mock instead ofNone).tests/test_utils.py::test_select_sft_generate_example_multiturn(new) — direct multi-turndata with no transform,
MultiturnJSON-style data with a transform, and multi-user-turnconversations (confirms the last user turn is used).
Out of scope (follow-up)
litgpt/prompts.py'sprompt_stylesname-lookup dict doesn't register theChatML/R1Basebase classes by name — only
ChatML's specific subclasses (qwen2.5,qwen3,smollm2,salamandra) andLlama3are. So--data.prompt_style chatmlcurrentlyKeyErrors;--data.prompt_style llama3(or aChatMLsubclass name) works today. Worth a small follow-upsince
chatmlis the most likely first thing people try.litgpt chatconversation history,LLM.generate(messages=...))is unrelated to this data-loading PR.
deita.py/lima.pystill flatten multi-turn source data into independent single-turn pairsrather than routing through
MultiturnSFTDataset— not touched here.Test plan
pytest tests/data/test_multiturn_json.py tests/data/test_json.py tests/data/test_base.py tests/test_prompts.py tests/test_utils.py— 125 passing, 6 skipped (unrelated/environment-gated).ruff check/ruff format --checkclean.MultiturnJSONand plainJSONthroughselect_sft_generate_example, confirming no crash and correct output.