Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .agents/skills/fine-tuning/reference/grpo-training.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,14 @@ end-to-end provider deadline or public cancellation qualification. The first
executable candidate requires identical model and tokenizer revisions because
both consume one verified local snapshot.

The packaged runtime resolves `GRPOConfig` precision before loading the model.
When the retained model dtype is `null`, it passes the effective FP16 or BF16
dtype to Unsloth's loader. Pinned Unsloth checks that loaded dtype against
trainer precision during `GRPOTrainer` construction; allowing the loader to
auto-select BF16 on BF16-capable hardware while the recipe requests FP16 can
fail at `TRAINER_SETUP`. A closed `TypeError` marker alone cannot establish
that this was the historical cause, and it does not authorize replay.

GRPO has a separate fixed 131-member offline inventory and 13-member outer
closure. Existing SFT inventories remain unchanged. Maintenance uses the
checked-in command, offline first because the outer closure binds its bytes:
Expand Down
8 changes: 8 additions & 0 deletions .claude/skills/fine-tuning/reference/grpo-training.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,14 @@ end-to-end provider deadline or public cancellation qualification. The first
executable candidate requires identical model and tokenizer revisions because
both consume one verified local snapshot.

The packaged runtime resolves `GRPOConfig` precision before loading the model.
When the retained model dtype is `null`, it passes the effective FP16 or BF16
dtype to Unsloth's loader. Pinned Unsloth checks that loaded dtype against
trainer precision during `GRPOTrainer` construction; allowing the loader to
auto-select BF16 on BF16-capable hardware while the recipe requests FP16 can
fail at `TRAINER_SETUP`. A closed `TypeError` marker alone cannot establish
that this was the historical cause, and it does not authorize replay.

GRPO has a separate fixed 131-member offline inventory and 13-member outer
closure. Existing SFT inventories remain unchanged. Maintenance uses the
checked-in command, offline first because the outer closure binds its bytes:
Expand Down
8 changes: 8 additions & 0 deletions .skills/fine-tuning/reference/grpo-training.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,14 @@ end-to-end provider deadline or public cancellation qualification. The first
executable candidate requires identical model and tokenizer revisions because
both consume one verified local snapshot.

The packaged runtime resolves `GRPOConfig` precision before loading the model.
When the retained model dtype is `null`, it passes the effective FP16 or BF16
dtype to Unsloth's loader. Pinned Unsloth checks that loaded dtype against
trainer precision during `GRPOTrainer` construction; allowing the loader to
auto-select BF16 on BF16-capable hardware while the recipe requests FP16 can
fail at `TRAINER_SETUP`. A closed `TypeError` marker alone cannot establish
that this was the historical cause, and it does not authorize replay.

GRPO has a separate fixed 131-member offline inventory and 13-member outer
closure. Existing SFT inventories remain unchanged. Maintenance uses the
checked-in command, offline first because the outer closure binds its bytes:
Expand Down
8 changes: 7 additions & 1 deletion Trainers/grpo/runtime_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,10 +468,16 @@ def mark(value):
args = build_grpo_config(config, output_root / 'checkpoints',
config_class=deps['config_class'], bf16_supported=deps['bf16_supported'])
helper = deps['model_loader']
# Unsloth's patched GRPOTrainer checks the loaded model dtype against the
# effective GRPOConfig precision. Its loader otherwise chooses BF16 on a
# BF16-capable GPU even when the retained recipe requested FP16 training.
model_dtype = config['model'].get('dtype')
if model_dtype is None:
model_dtype = 'bfloat16' if args.bf16 else 'float16' if args.fp16 else None
mark("MODEL_LIBRARY_LOAD")
model, tokenizer, is_vl = helper.load_model_and_tokenizer(
str(model_snapshot), max_seq_length=config['model']['max_seq_length'],
dtype=config['model'].get('dtype'), load_in_4bit=config['model'].get('load_in_4bit', True),
dtype=model_dtype, load_in_4bit=config['model'].get('load_in_4bit', True),
offline=True)
if is_vl:
raise ValueError("retained GRPO requires text model")
Expand Down
32 changes: 32 additions & 0 deletions tests/trainers/grpo/test_runtime_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,38 @@ def test_real_flow_offline_rewards_metrics_and_mandatory_roles(tmp_path, monkeyp
assert 'parameter_updated' not in result['training_lineage'].read_text()


@pytest.mark.parametrize(('fp16', 'bf16', 'supported', 'expected'), [
(True, False, True, 'float16'),
(False, True, True, 'bfloat16'),
(False, True, False, 'float16'),
])
def test_model_load_precision_matches_effective_trainer_precision(
tmp_path, fp16, bf16, supported, expected):
values = inputs(tmp_path)
values['config']['training'].update(fp16=fp16, bf16=bf16)
values['workload']['config_sha256'] = runtime._sha(runtime._canonical(values['config']))
events = []
deps = dependencies(events)()
deps['bf16_supported'] = supported
def load(*args, **kwargs):
events.append(('model', args, kwargs))
return SimpleNamespace(config=SimpleNamespace(dtype=kwargs['dtype'])), Tokenizer(), False
deps['model_loader'].load_model_and_tokenizer = load
trainer_base = deps['trainer_class']
class PrecisionCheckingTrainer(trainer_base):
def __init__(self, **kwargs):
dtype = kwargs['model'].config.dtype
precision = kwargs['args']
if (dtype == 'float16' and precision.bf16) or (dtype != 'float16' and precision.fp16):
raise TypeError('incompatible model and trainer precision')
super().__init__(**kwargs)
deps['trainer_class'] = PrecisionCheckingTrainer
runtime.run_grpo(**values, _dependency_factory=lambda: deps)
assert events[0][2]['dtype'] == expected
assert (events[1][1]['args'].fp16, events[1][1]['args'].bf16) == (
expected == 'float16', expected == 'bfloat16')


@pytest.mark.parametrize('change', ['dataset', 'reward', 'config', 'model', 'method', 'nonfinite', 'bool', 'custom', 'pivot', 'empty'])
def test_rejects_before_dependency_effects(tmp_path, change):
values = inputs(tmp_path)
Expand Down
2 changes: 1 addition & 1 deletion tuner/runtime/manifests/offline-grpo-worker-v1.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tuner/runtime/manifests/packaged-grpo-worker-v1.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"closure_digest":"56f676b31466079fb81fa402392eb9ce4634a686526946ad9280299a34780406","members":[{"path":"tuner/runtime/manifests/offline-grpo-worker-v1.json","sha256":"59b017179339d58ff24e52d77e85d7d8cf755f25fdf93217ce88f8f9129e32db","size_bytes":21344},{"path":"tuner/runtime/offline_grpo_worker.py","sha256":"aac831048855e959598a4dc45b29e614f06f8714df18e868fd6b069e3c765710","size_bytes":9323},{"path":"tuner/runtime/offline_sft_worker.py","sha256":"2304eeb00667954510c34cc4aacaa818532a301fbeabf04c2fb165d6cd1d70b5","size_bytes":36343},{"path":"tuner/runtime/packaged_grpo_child.py","sha256":"124d5b2a969d38ac2e488d58a216759108f1a55dfcc3dcaf1012791efe792e4b","size_bytes":8273},{"path":"tuner/runtime/packaged_grpo_execution.py","sha256":"a77c7d6b97ad2ded3b9631174e479e60462f1894513f90fd2fc7798828c6867d","size_bytes":14943},{"path":"tuner/runtime/packaged_grpo_verification.py","sha256":"56564c6ee6414a3a71ce7c9ef330152d812fede2a711a2329463a0b81a61b0d6","size_bytes":10319},{"path":"tuner/runtime/packaged_grpo_worker_closure.py","sha256":"81da8db5a61e9a874234d1e50c529e1056282fd0eef133b2720eb0f34f78cddc","size_bytes":3735},{"path":"tuner/runtime/packaged_sft_child.py","sha256":"ab41eb7dca28b440c043f589d297dc0d9f718ed14f6cb36c96d7bd1b7bfcdc14","size_bytes":23556},{"path":"tuner/runtime/packaged_sft_execution.py","sha256":"cab2b5032d16146422398305120f52c5a124287ecf929b640a1f17d0ea2ed648","size_bytes":80053},{"path":"tuner/runtime/packaged_training_worker.py","sha256":"9cfe13dc9f0124b4da01d05e92cf3c2c9249d075c3a508d6b93b0a7c7f0e109f","size_bytes":34986},{"path":"tuner/runtime/packaged_worker_closure.py","sha256":"e457937426e19747e867a314c1a2556ee64bd5cbc19359f535253b02403d1443","size_bytes":8705},{"path":"tuner/runtime/releases.py","sha256":"c0aa7ba37e8d35287fdd480cf0c76053a554e546c40c8bf36f5cf05d1a96d22b","size_bytes":52535},{"path":"tuner/training/packaged_grpo_compilation.py","sha256":"d186a05ffb8cc756e782b2bb9a3f66e925779104b70045bd56a5592846fec524","size_bytes":8722}],"schema_version":"synaptic-packaged-grpo-worker-closure/v1"}
{"closure_digest":"e48763871e7e9bc50c0c2b218bbfcb885b72b56f2ae1c2c41b1e16938c198ffe","members":[{"path":"tuner/runtime/manifests/offline-grpo-worker-v1.json","sha256":"7159247a2d15faaf2654b14168c1f90359b09950f646d0e9b2e2ec683f20617b","size_bytes":21344},{"path":"tuner/runtime/offline_grpo_worker.py","sha256":"aac831048855e959598a4dc45b29e614f06f8714df18e868fd6b069e3c765710","size_bytes":9323},{"path":"tuner/runtime/offline_sft_worker.py","sha256":"2304eeb00667954510c34cc4aacaa818532a301fbeabf04c2fb165d6cd1d70b5","size_bytes":36343},{"path":"tuner/runtime/packaged_grpo_child.py","sha256":"124d5b2a969d38ac2e488d58a216759108f1a55dfcc3dcaf1012791efe792e4b","size_bytes":8273},{"path":"tuner/runtime/packaged_grpo_execution.py","sha256":"a77c7d6b97ad2ded3b9631174e479e60462f1894513f90fd2fc7798828c6867d","size_bytes":14943},{"path":"tuner/runtime/packaged_grpo_verification.py","sha256":"56564c6ee6414a3a71ce7c9ef330152d812fede2a711a2329463a0b81a61b0d6","size_bytes":10319},{"path":"tuner/runtime/packaged_grpo_worker_closure.py","sha256":"81da8db5a61e9a874234d1e50c529e1056282fd0eef133b2720eb0f34f78cddc","size_bytes":3735},{"path":"tuner/runtime/packaged_sft_child.py","sha256":"ab41eb7dca28b440c043f589d297dc0d9f718ed14f6cb36c96d7bd1b7bfcdc14","size_bytes":23556},{"path":"tuner/runtime/packaged_sft_execution.py","sha256":"cab2b5032d16146422398305120f52c5a124287ecf929b640a1f17d0ea2ed648","size_bytes":80053},{"path":"tuner/runtime/packaged_training_worker.py","sha256":"9cfe13dc9f0124b4da01d05e92cf3c2c9249d075c3a508d6b93b0a7c7f0e109f","size_bytes":34986},{"path":"tuner/runtime/packaged_worker_closure.py","sha256":"e457937426e19747e867a314c1a2556ee64bd5cbc19359f535253b02403d1443","size_bytes":8705},{"path":"tuner/runtime/releases.py","sha256":"c0aa7ba37e8d35287fdd480cf0c76053a554e546c40c8bf36f5cf05d1a96d22b","size_bytes":52535},{"path":"tuner/training/packaged_grpo_compilation.py","sha256":"d186a05ffb8cc756e782b2bb9a3f66e925779104b70045bd56a5592846fec524","size_bytes":8722}],"schema_version":"synaptic-packaged-grpo-worker-closure/v1"}
Loading