inductive-mlxrl 0.1.2__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/CHANGELOG.md +21 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/CONTRIBUTING.md +21 -1
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/DESIGN.md +38 -4
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/PKG-INFO +63 -6
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/README.md +62 -5
- inductive_mlxrl-0.2.0/examples/qwen35_9b_g2_checkpoint.toml +29 -0
- inductive_mlxrl-0.2.0/examples/qwen3_0_6b_gigpo_recurring.toml +33 -0
- inductive_mlxrl-0.2.0/examples/qwen3_0_6b_grpo.toml +30 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/__init__.py +1 -2
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/algo/__init__.py +12 -0
- inductive_mlxrl-0.2.0/mlxrl/algo/gigpo.py +202 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/algorithm.py +38 -1
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/cli.py +142 -2
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/config.py +48 -6
- inductive_mlxrl-0.2.0/mlxrl/env/__init__.py +27 -0
- inductive_mlxrl-0.2.0/mlxrl/env/base.py +75 -0
- inductive_mlxrl-0.2.0/mlxrl/env/reference.py +104 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/policy/__init__.py +10 -0
- inductive_mlxrl-0.2.0/mlxrl/policy/trajectory_logprobs.py +130 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/rollout/__init__.py +10 -0
- inductive_mlxrl-0.2.0/mlxrl/rollout/agentic.py +396 -0
- inductive_mlxrl-0.2.0/mlxrl/train/__init__.py +18 -0
- inductive_mlxrl-0.2.0/mlxrl/train/trajectory.py +276 -0
- inductive_mlxrl-0.2.0/mlxrl/trajectory.py +156 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/pyproject.toml +2 -1
- inductive_mlxrl-0.2.0/tests/test_agentic_env.py +98 -0
- inductive_mlxrl-0.2.0/tests/test_agentic_rollout.py +140 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_cli.py +37 -1
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_config.py +66 -0
- inductive_mlxrl-0.2.0/tests/test_gigpo.py +141 -0
- inductive_mlxrl-0.2.0/tests/test_train_trajectory.py +117 -0
- inductive_mlxrl-0.2.0/tests/test_trajectory_logprobs.py +89 -0
- inductive_mlxrl-0.1.2/mlxrl/train/__init__.py +0 -5
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/.gitignore +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/LICENSE +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/THIRD_PARTY_LICENSES.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/__init__.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/audit_phase4_config.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/external_baseline_worker.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after_final.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after_final.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after_noprefixsync.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after_noprefixsync.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_before.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_before.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate2_unified_smoke.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate2_unified_smoke.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_mlx_tune_sync_256.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_mlx_tune_sync_256.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_sync_blocked.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_sync_blocked.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_sync_smoke.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_sync_smoke.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate4_config_audit.json +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate4_config_audit.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate4_sampler_smoke.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate4_sampler_smoke.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate5_full_reconciled.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate5_full_reconciled.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_external_smoke.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_external_smoke.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_full_all_targets.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_full_all_targets.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_local_mlxrl_vs_mlx_lm.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_local_mlxrl_vs_mlx_lm.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_mlx_lm_g4_smoke.jsonl +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_mlx_lm_g4_smoke.md +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/run_phase4.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/algo/grpo.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/data/__init__.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/data/gsm8k.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/data/rewards.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/policy/logprobs.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/policy/model.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/py.typed +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/rollout/naive.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/rollout/optimized.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/train/grpo.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_benchmark_metrics.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_grpo.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_import_direction.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_optimized_rollout.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_policy_logprobs.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_policy_model.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_rewards.py +0 -0
- {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_train_grpo.py +0 -0
|
@@ -5,6 +5,27 @@ All notable changes to this project are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project uses semantic versioning once releases are cut.
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-06-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Multi-turn agentic rollout path with Gym-style `Environment` / `EnvFactory`
|
|
13
|
+
protocols and deterministic reference environments.
|
|
14
|
+
- Trajectory data model with action-token spans and full-forward trajectory
|
|
15
|
+
logprob gathering for 4-bit policy, old-policy, and reference semantics.
|
|
16
|
+
- GiGPO (`Group-in-Group Policy Optimization`) with episode-level and
|
|
17
|
+
anchor-state step-level advantages, plus an exact `omega=0` reduction gate.
|
|
18
|
+
- Trajectory batch builder and optimizer step that preserve the existing
|
|
19
|
+
single-turn GRPO-family APIs.
|
|
20
|
+
- Config-driven GiGPO training route with `max_turns`, `rollout_mode`,
|
|
21
|
+
`env_name`, `max_observation_len`, `gigpo_omega`, `gigpo_gamma`, and
|
|
22
|
+
normalization knobs.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Extend the memory estimator to label multi-turn configs as estimated
|
|
27
|
+
OOM-risk shapes while preserving the measured v0.1 single-turn anchors.
|
|
28
|
+
|
|
8
29
|
## [0.1.2] - 2026-06-24
|
|
9
30
|
|
|
10
31
|
### Fixed
|
|
@@ -20,7 +20,9 @@ estimator anchor tests are part of the contract.
|
|
|
20
20
|
|
|
21
21
|
## Adding An Algorithm
|
|
22
22
|
|
|
23
|
-
Use `RLOOAlgorithm` in `mlxrl/algo/grpo.py` as the smallest
|
|
23
|
+
Use `RLOOAlgorithm` in `mlxrl/algo/grpo.py` as the smallest single-turn
|
|
24
|
+
template. Use `GiGPOAlgorithm` in `mlxrl/algo/gigpo.py` as the trajectory
|
|
25
|
+
template.
|
|
24
26
|
|
|
25
27
|
Required steps:
|
|
26
28
|
|
|
@@ -35,6 +37,23 @@ Required steps:
|
|
|
35
37
|
If an algorithm needs to drop or reshape examples before the loss, use the
|
|
36
38
|
`filter_batch` hook. Do not special-case it in the trainer.
|
|
37
39
|
|
|
40
|
+
Trajectory algorithms implement `TrajectoryAlgorithm`, ship a hand-computed
|
|
41
|
+
multi-turn loss/gradient test, and prove their reduction to an existing
|
|
42
|
+
objective when a degenerate config should do so. They may consume trajectory
|
|
43
|
+
metadata such as env state ids, but `rollout/`, `policy/`, and `train/` still
|
|
44
|
+
must not import concrete algorithms.
|
|
45
|
+
|
|
46
|
+
## Adding An Environment
|
|
47
|
+
|
|
48
|
+
External envs integrate through `EnvFactory(task, seed, group_index) ->
|
|
49
|
+
Environment`. Implement `reset()`, `step(action)`, `max_turns`, and
|
|
50
|
+
`state_id(observation)`. The env owns the anchor-state definition; the rollout
|
|
51
|
+
engine only records ids, and GiGPO groups them later.
|
|
52
|
+
|
|
53
|
+
Keep envs deterministic under the provided seed when possible. Add a tiny test
|
|
54
|
+
that resets, steps, returns stable state ids, and exercises truncation or
|
|
55
|
+
termination. Do not add omega gym itself to this repo.
|
|
56
|
+
|
|
38
57
|
## MLX And CI
|
|
39
58
|
|
|
40
59
|
GitHub-hosted Linux runners cannot run the MLX/Metal path. Linux CI gates the
|
|
@@ -105,6 +124,7 @@ In scope:
|
|
|
105
124
|
- single-process Apple Silicon RL post-training;
|
|
106
125
|
- QLoRA on local MLX LLMs;
|
|
107
126
|
- critic-free on-policy algorithms;
|
|
127
|
+
- multi-turn agentic environments through the small env protocol;
|
|
108
128
|
- memory-conscious rollout and training paths.
|
|
109
129
|
|
|
110
130
|
Out of scope:
|
|
@@ -7,10 +7,10 @@ fast enough that rollout is the main problem and the loss stays thin.
|
|
|
7
7
|
## Critic-Free By Design
|
|
8
8
|
|
|
9
9
|
The library is intentionally limited to rollout-based policy-gradient methods:
|
|
10
|
-
GRPO, Dr. GRPO, DAPO, GSPO, and
|
|
11
|
-
critic/value model path, a different forward pass, and a different
|
|
12
|
-
profile. DPO and ORPO are also out of scope because they are offline
|
|
13
|
-
objectives rather than on-policy rollout algorithms.
|
|
10
|
+
GRPO, Dr. GRPO, DAPO, GSPO, RLOO, and GiGPO. PPO is out of scope because it
|
|
11
|
+
needs a critic/value model path, a different forward pass, and a different
|
|
12
|
+
memory profile. DPO and ORPO are also out of scope because they are offline
|
|
13
|
+
preference objectives rather than on-policy rollout algorithms.
|
|
14
14
|
|
|
15
15
|
That boundary matters. `mlxrl` keeps one policy model object in memory, attaches
|
|
16
16
|
QLoRA adapters to it, and computes reference logprobs by disabling those
|
|
@@ -32,6 +32,24 @@ stable while algorithms change. DAPO's dynamic sampling is the proof that the
|
|
|
32
32
|
interface is more general than "GRPO with renamed constants": it drops
|
|
33
33
|
zero-advantage groups through `filter_batch` without special trainer branches.
|
|
34
34
|
|
|
35
|
+
Multi-turn algorithms use the sibling `TrajectoryAlgorithm` protocol rather
|
|
36
|
+
than widening the single-turn `Algorithm` contract. GiGPO is the first
|
|
37
|
+
implementation: the rollout engine records env-provided state ids, while the
|
|
38
|
+
algorithm owns anchor-state grouping and credit assignment.
|
|
39
|
+
|
|
40
|
+
## Agentic Environment Seam
|
|
41
|
+
|
|
42
|
+
Agentic rollout is built around `EnvFactory(task, seed, group_index) ->
|
|
43
|
+
Environment`. An environment exposes `reset()`, `step(action)`, `max_turns`,
|
|
44
|
+
and `state_id(observation)`. The state id is deliberately supplied by the env:
|
|
45
|
+
omega gym, WebShop, ALFWorld, or a toy env each decides what counts as the same
|
|
46
|
+
anchor state.
|
|
47
|
+
|
|
48
|
+
The engine records observations, actions, rewards, state ids, full token
|
|
49
|
+
history, and action-token spans. It does not compute anchor groups. This keeps
|
|
50
|
+
rollout reusable for future trajectory algorithms while GiGPO performs the
|
|
51
|
+
group-in-group advantage calculation offline from the same trajectories.
|
|
52
|
+
|
|
35
53
|
## Correctness Gates
|
|
36
54
|
|
|
37
55
|
Speed changes are allowed only behind equivalence gates. The original Phase 1
|
|
@@ -40,6 +58,11 @@ token-for-token at fixed seed and match loss within tolerance. The protocol
|
|
|
40
58
|
refactor was checked against the pre-refactor GRPO and Dr. GRPO losses with
|
|
41
59
|
zero loss and adapter-gradient difference.
|
|
42
60
|
|
|
61
|
+
GiGPO adds two reduction gates: `omega=0` must match episode-level GRPO over the
|
|
62
|
+
same trajectories, and single-turn one-step trajectories with `omega=0` must
|
|
63
|
+
match the v0.1 GRPO path through loss and adapter gradient. Toy multi-turn
|
|
64
|
+
cases also carry hand-computed advantage, loss, and gradient checks.
|
|
65
|
+
|
|
43
66
|
Old-policy changes use a stronger gate than naive equality. At 4-bit, the
|
|
44
67
|
rollout-time cached decode realization and the full-forward realization are not
|
|
45
68
|
numerically identical. Stored rollout logprobs are attractive because they are
|
|
@@ -56,6 +79,11 @@ for gradient-bearing or importance-weighting quantities. Anything used in the
|
|
|
56
79
|
loss denominator, KL, or adapter gradient must come from the full-forward path
|
|
57
80
|
unless a dedicated stability gate proves otherwise.
|
|
58
81
|
|
|
82
|
+
For multi-turn trajectories, this means policy, old-policy, and reference
|
|
83
|
+
logprobs are gathered from a full forward over the complete token history, then
|
|
84
|
+
masked down to action-token spans only. Rollout-time cached logprobs remain
|
|
85
|
+
diagnostic data, not training data.
|
|
86
|
+
|
|
59
87
|
## Memory As A First-Class Constraint
|
|
60
88
|
|
|
61
89
|
Apple Silicon's unified memory is the deployment target, not an afterthought.
|
|
@@ -70,3 +98,9 @@ anchors and labels long-sequence uncheckpointed hybrid configs as OOM-risk
|
|
|
70
98
|
estimates, not measurements. Its purpose is to nudge users toward the knob that
|
|
71
99
|
will most likely make a run fit: enable checkpointing, reduce G, then reduce
|
|
72
100
|
completion length.
|
|
101
|
+
|
|
102
|
+
Multi-turn configs add trajectory length as `prompt + turns * action_tokens +
|
|
103
|
+
(turns - 1) * observation_tokens`. Parallel-per-turn rollout keeps G
|
|
104
|
+
per-trajectory cache/state objects resident; sequential mode is the lower-memory
|
|
105
|
+
fallback. Until more measured anchors exist, multi-turn estimates are labeled as
|
|
106
|
+
OOM-risk estimates rather than confident measurements.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: inductive-mlxrl
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Small single-process RL post-training for LLMs on Apple Silicon with MLX.
|
|
5
5
|
Project-URL: Homepage, https://github.com/inductiveML/mlxrl
|
|
6
6
|
Project-URL: Repository, https://github.com/inductiveML/mlxrl
|
|
@@ -25,16 +25,18 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
|
|
26
26
|
# mlxrl
|
|
27
27
|
|
|
28
|
-
Fast on-policy MLX RL for Apple Silicon
|
|
29
|
-
preference tuning, and
|
|
28
|
+
Fast on-policy MLX RL for Apple Silicon, including single-turn GRPO-family RL
|
|
29
|
+
and multi-turn GiGPO; not a general RL framework, not preference tuning, and
|
|
30
|
+
not distributed training.
|
|
30
31
|
|
|
31
32
|
`mlxrl` is a small, single-process RL post-training library for LLMs on Apple
|
|
32
33
|
Silicon. It is built around one idea: GRPO on MLX should be a fast batched
|
|
33
34
|
rollout path with a thin loss and optimizer step on top, not a framework.
|
|
34
35
|
|
|
35
|
-
The current implementation targets QLoRA GRPO on local 4-bit MLX models.
|
|
36
|
-
reuses `mlx-lm` model loading, LoRA layers, KV caches, and sampling
|
|
37
|
-
and keeps generation and training in one Python process with one
|
|
36
|
+
The current implementation targets QLoRA GRPO/GiGPO on local 4-bit MLX models.
|
|
37
|
+
It reuses `mlx-lm` model loading, LoRA layers, KV caches, and sampling
|
|
38
|
+
utilities, and keeps generation and training in one Python process with one
|
|
39
|
+
model object.
|
|
38
40
|
|
|
39
41
|
`mlxrl` is pre-1.0. The correctness gates are stable, but import APIs and config
|
|
40
42
|
fields may change before a 1.0 release.
|
|
@@ -66,6 +68,9 @@ UV_CACHE_DIR=.uv-cache uv run mlxrl train \
|
|
|
66
68
|
- Adapter-disabled reference policy on the same model object.
|
|
67
69
|
- GRPO, Dr. GRPO, DAPO, and GSPO loss variants.
|
|
68
70
|
- RLOO (REINFORCE Leave-One-Out) as a critic-free rollout objective.
|
|
71
|
+
- Multi-turn agentic rollout through an `Environment` / `EnvFactory` protocol.
|
|
72
|
+
- GiGPO with episode-level and anchor-state step-level advantages.
|
|
73
|
+
- Full-forward trajectory action-logprob recompute for 4-bit correctness.
|
|
69
74
|
- QLoRA injection on dense and heterogeneous/hybrid layer stacks.
|
|
70
75
|
- Qwen3.5-style hybrid support via MLX-LM auto LoRA targeting, including
|
|
71
76
|
DeltaNet `linear_attn.in_proj_*` and dense attention `q/k/v/o_proj`.
|
|
@@ -159,6 +164,14 @@ UV_CACHE_DIR=.uv-cache uv run mlxrl train \
|
|
|
159
164
|
--available-memory-gb 48
|
|
160
165
|
```
|
|
161
166
|
|
|
167
|
+
Reference multi-turn GiGPO run against the built-in recurring-state env:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
UV_CACHE_DIR=.uv-cache uv run mlxrl train \
|
|
171
|
+
--config examples/qwen3_0_6b_gigpo_recurring.toml \
|
|
172
|
+
--available-memory-gb 48
|
|
173
|
+
```
|
|
174
|
+
|
|
162
175
|
The config schema validates model id, quant bits, group size, completion/prompt
|
|
163
176
|
lengths, checkpointing granularity, `iogpu.wired_limit_mb`, optimizer settings,
|
|
164
177
|
algorithm hyperparameters, KL beta, and seed before a model is loaded. CLI
|
|
@@ -243,6 +256,45 @@ metrics = optimizer_step(
|
|
|
243
256
|
)
|
|
244
257
|
```
|
|
245
258
|
|
|
259
|
+
Multi-turn GiGPO uses the additive trajectory path:
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from mlxrl.algo import GiGPOAlgorithm
|
|
263
|
+
from mlxrl.env import RecurringStateTextEnv
|
|
264
|
+
from mlxrl.rollout import generate_agentic_trajectories
|
|
265
|
+
from mlxrl.train import batch_from_trajectories, optimizer_step_trajectory
|
|
266
|
+
|
|
267
|
+
algorithm = GiGPOAlgorithm(omega=1.0, gamma=1.0)
|
|
268
|
+
trajectories = generate_agentic_trajectories(
|
|
269
|
+
model=model,
|
|
270
|
+
tokenizer=tokenizer,
|
|
271
|
+
env_factory=lambda task, seed, group: RecurringStateTextEnv(
|
|
272
|
+
task=str(task),
|
|
273
|
+
max_turns=3,
|
|
274
|
+
),
|
|
275
|
+
tasks=["finish the task"],
|
|
276
|
+
group_size=4,
|
|
277
|
+
sampling=sampling,
|
|
278
|
+
rollout_mode="parallel_per_turn",
|
|
279
|
+
)
|
|
280
|
+
batch = batch_from_trajectories(
|
|
281
|
+
model=model,
|
|
282
|
+
trajectories=trajectories,
|
|
283
|
+
group_size=4,
|
|
284
|
+
pad_token_id=pad_token_id,
|
|
285
|
+
algorithm=algorithm,
|
|
286
|
+
compute_reference=beta != 0.0,
|
|
287
|
+
)
|
|
288
|
+
metrics = optimizer_step_trajectory(
|
|
289
|
+
model=model,
|
|
290
|
+
optimizer=optimizer,
|
|
291
|
+
batch=batch,
|
|
292
|
+
beta=beta,
|
|
293
|
+
pad_token_id=pad_token_id,
|
|
294
|
+
algorithm=algorithm,
|
|
295
|
+
)
|
|
296
|
+
```
|
|
297
|
+
|
|
246
298
|
`micro_batch_size=0` keeps the original whole-batch path. Micro-batching is
|
|
247
299
|
currently exact for token-mean policy losses: base GRPO, DAPO, GSPO token mode,
|
|
248
300
|
RLOO, and Dr. GRPO with `loss_reduction="token_mean"`. Sequence-reduced losses
|
|
@@ -278,6 +330,7 @@ import concrete algorithm implementations.
|
|
|
278
330
|
| DAPO | asymmetric low/high clipping plus optional dynamic zero-advantage group filtering |
|
|
279
331
|
| GSPO | sequence-level, length-normalized importance ratio and clipping |
|
|
280
332
|
| RLOO | leave-one-out group baseline, no critic, no std-normalized advantage |
|
|
333
|
+
| GiGPO | multi-turn episode advantage plus anchor-state step advantage |
|
|
281
334
|
|
|
282
335
|
## Memory Preflight
|
|
283
336
|
|
|
@@ -296,6 +349,9 @@ Qwen3.5-9B/G2/seq609/per-layer-checkpointed, `45.9 GB` for
|
|
|
296
349
|
Qwen3.5-9B/G4/seq609/per-layer-checkpointed, and `36 GB` for
|
|
297
350
|
Qwen3.5-9B/G2/seq128/no-checkpoint. For hybrid 9B no-checkpoint long-sequence
|
|
298
351
|
configs, it reports an OOM-risk lower bound rather than a fake precise peak.
|
|
352
|
+
Multi-turn configs include `max_turns` and `max_observation_len` in the
|
|
353
|
+
estimated trajectory length and are labeled as estimated OOM-risk shapes until
|
|
354
|
+
new measured anchors are added.
|
|
299
355
|
For an obviously too-large Qwen3.5-9B/G8/prompt97/T512/no-checkpoint config on
|
|
300
356
|
48 GB, it flags the run and suggests the measured-boundary fallback around
|
|
301
357
|
G4/T512/checkpointed.
|
|
@@ -375,6 +431,7 @@ updates, or benchmark timing boundaries.
|
|
|
375
431
|
```text
|
|
376
432
|
mlxrl/
|
|
377
433
|
rollout/ # batched group generation
|
|
434
|
+
env/ # agentic environment protocols and reference envs
|
|
378
435
|
policy/ # model loading, LoRA setup, logprob passes
|
|
379
436
|
algo/ # GRPO-family advantages and losses
|
|
380
437
|
train/ # value_and_grad and optimizer integration
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# mlxrl
|
|
2
2
|
|
|
3
|
-
Fast on-policy MLX RL for Apple Silicon
|
|
4
|
-
preference tuning, and
|
|
3
|
+
Fast on-policy MLX RL for Apple Silicon, including single-turn GRPO-family RL
|
|
4
|
+
and multi-turn GiGPO; not a general RL framework, not preference tuning, and
|
|
5
|
+
not distributed training.
|
|
5
6
|
|
|
6
7
|
`mlxrl` is a small, single-process RL post-training library for LLMs on Apple
|
|
7
8
|
Silicon. It is built around one idea: GRPO on MLX should be a fast batched
|
|
8
9
|
rollout path with a thin loss and optimizer step on top, not a framework.
|
|
9
10
|
|
|
10
|
-
The current implementation targets QLoRA GRPO on local 4-bit MLX models.
|
|
11
|
-
reuses `mlx-lm` model loading, LoRA layers, KV caches, and sampling
|
|
12
|
-
and keeps generation and training in one Python process with one
|
|
11
|
+
The current implementation targets QLoRA GRPO/GiGPO on local 4-bit MLX models.
|
|
12
|
+
It reuses `mlx-lm` model loading, LoRA layers, KV caches, and sampling
|
|
13
|
+
utilities, and keeps generation and training in one Python process with one
|
|
14
|
+
model object.
|
|
13
15
|
|
|
14
16
|
`mlxrl` is pre-1.0. The correctness gates are stable, but import APIs and config
|
|
15
17
|
fields may change before a 1.0 release.
|
|
@@ -41,6 +43,9 @@ UV_CACHE_DIR=.uv-cache uv run mlxrl train \
|
|
|
41
43
|
- Adapter-disabled reference policy on the same model object.
|
|
42
44
|
- GRPO, Dr. GRPO, DAPO, and GSPO loss variants.
|
|
43
45
|
- RLOO (REINFORCE Leave-One-Out) as a critic-free rollout objective.
|
|
46
|
+
- Multi-turn agentic rollout through an `Environment` / `EnvFactory` protocol.
|
|
47
|
+
- GiGPO with episode-level and anchor-state step-level advantages.
|
|
48
|
+
- Full-forward trajectory action-logprob recompute for 4-bit correctness.
|
|
44
49
|
- QLoRA injection on dense and heterogeneous/hybrid layer stacks.
|
|
45
50
|
- Qwen3.5-style hybrid support via MLX-LM auto LoRA targeting, including
|
|
46
51
|
DeltaNet `linear_attn.in_proj_*` and dense attention `q/k/v/o_proj`.
|
|
@@ -134,6 +139,14 @@ UV_CACHE_DIR=.uv-cache uv run mlxrl train \
|
|
|
134
139
|
--available-memory-gb 48
|
|
135
140
|
```
|
|
136
141
|
|
|
142
|
+
Reference multi-turn GiGPO run against the built-in recurring-state env:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
UV_CACHE_DIR=.uv-cache uv run mlxrl train \
|
|
146
|
+
--config examples/qwen3_0_6b_gigpo_recurring.toml \
|
|
147
|
+
--available-memory-gb 48
|
|
148
|
+
```
|
|
149
|
+
|
|
137
150
|
The config schema validates model id, quant bits, group size, completion/prompt
|
|
138
151
|
lengths, checkpointing granularity, `iogpu.wired_limit_mb`, optimizer settings,
|
|
139
152
|
algorithm hyperparameters, KL beta, and seed before a model is loaded. CLI
|
|
@@ -218,6 +231,45 @@ metrics = optimizer_step(
|
|
|
218
231
|
)
|
|
219
232
|
```
|
|
220
233
|
|
|
234
|
+
Multi-turn GiGPO uses the additive trajectory path:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from mlxrl.algo import GiGPOAlgorithm
|
|
238
|
+
from mlxrl.env import RecurringStateTextEnv
|
|
239
|
+
from mlxrl.rollout import generate_agentic_trajectories
|
|
240
|
+
from mlxrl.train import batch_from_trajectories, optimizer_step_trajectory
|
|
241
|
+
|
|
242
|
+
algorithm = GiGPOAlgorithm(omega=1.0, gamma=1.0)
|
|
243
|
+
trajectories = generate_agentic_trajectories(
|
|
244
|
+
model=model,
|
|
245
|
+
tokenizer=tokenizer,
|
|
246
|
+
env_factory=lambda task, seed, group: RecurringStateTextEnv(
|
|
247
|
+
task=str(task),
|
|
248
|
+
max_turns=3,
|
|
249
|
+
),
|
|
250
|
+
tasks=["finish the task"],
|
|
251
|
+
group_size=4,
|
|
252
|
+
sampling=sampling,
|
|
253
|
+
rollout_mode="parallel_per_turn",
|
|
254
|
+
)
|
|
255
|
+
batch = batch_from_trajectories(
|
|
256
|
+
model=model,
|
|
257
|
+
trajectories=trajectories,
|
|
258
|
+
group_size=4,
|
|
259
|
+
pad_token_id=pad_token_id,
|
|
260
|
+
algorithm=algorithm,
|
|
261
|
+
compute_reference=beta != 0.0,
|
|
262
|
+
)
|
|
263
|
+
metrics = optimizer_step_trajectory(
|
|
264
|
+
model=model,
|
|
265
|
+
optimizer=optimizer,
|
|
266
|
+
batch=batch,
|
|
267
|
+
beta=beta,
|
|
268
|
+
pad_token_id=pad_token_id,
|
|
269
|
+
algorithm=algorithm,
|
|
270
|
+
)
|
|
271
|
+
```
|
|
272
|
+
|
|
221
273
|
`micro_batch_size=0` keeps the original whole-batch path. Micro-batching is
|
|
222
274
|
currently exact for token-mean policy losses: base GRPO, DAPO, GSPO token mode,
|
|
223
275
|
RLOO, and Dr. GRPO with `loss_reduction="token_mean"`. Sequence-reduced losses
|
|
@@ -253,6 +305,7 @@ import concrete algorithm implementations.
|
|
|
253
305
|
| DAPO | asymmetric low/high clipping plus optional dynamic zero-advantage group filtering |
|
|
254
306
|
| GSPO | sequence-level, length-normalized importance ratio and clipping |
|
|
255
307
|
| RLOO | leave-one-out group baseline, no critic, no std-normalized advantage |
|
|
308
|
+
| GiGPO | multi-turn episode advantage plus anchor-state step advantage |
|
|
256
309
|
|
|
257
310
|
## Memory Preflight
|
|
258
311
|
|
|
@@ -271,6 +324,9 @@ Qwen3.5-9B/G2/seq609/per-layer-checkpointed, `45.9 GB` for
|
|
|
271
324
|
Qwen3.5-9B/G4/seq609/per-layer-checkpointed, and `36 GB` for
|
|
272
325
|
Qwen3.5-9B/G2/seq128/no-checkpoint. For hybrid 9B no-checkpoint long-sequence
|
|
273
326
|
configs, it reports an OOM-risk lower bound rather than a fake precise peak.
|
|
327
|
+
Multi-turn configs include `max_turns` and `max_observation_len` in the
|
|
328
|
+
estimated trajectory length and are labeled as estimated OOM-risk shapes until
|
|
329
|
+
new measured anchors are added.
|
|
274
330
|
For an obviously too-large Qwen3.5-9B/G8/prompt97/T512/no-checkpoint config on
|
|
275
331
|
48 GB, it flags the run and suggests the measured-boundary fallback around
|
|
276
332
|
G4/T512/checkpointed.
|
|
@@ -350,6 +406,7 @@ updates, or benchmark timing boundaries.
|
|
|
350
406
|
```text
|
|
351
407
|
mlxrl/
|
|
352
408
|
rollout/ # batched group generation
|
|
409
|
+
env/ # agentic environment protocols and reference envs
|
|
353
410
|
policy/ # model loading, LoRA setup, logprob passes
|
|
354
411
|
algo/ # GRPO-family advantages and losses
|
|
355
412
|
train/ # value_and_grad and optimizer integration
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
model_id = "mlx-community/Qwen3.5-9B-MLX-4bit"
|
|
2
|
+
quant_bits = 4
|
|
3
|
+
group_size = 2
|
|
4
|
+
max_prompt_len = 97
|
|
5
|
+
max_completion_len = 512
|
|
6
|
+
steps = 1
|
|
7
|
+
seed = 7
|
|
8
|
+
kl_beta = 0.0
|
|
9
|
+
rank = 16
|
|
10
|
+
scale = 2.0
|
|
11
|
+
dropout = 0.0
|
|
12
|
+
gradient_checkpointing = true
|
|
13
|
+
checkpoint_granularity = "per-layer"
|
|
14
|
+
iogpu_wired_limit_mb = 49152
|
|
15
|
+
micro_batch_size = 0
|
|
16
|
+
output = "/tmp/mlxrl_qwen35_9b_g2_checkpoint.npz"
|
|
17
|
+
|
|
18
|
+
[optimizer]
|
|
19
|
+
name = "adam"
|
|
20
|
+
learning_rate = 0.00001
|
|
21
|
+
|
|
22
|
+
[sampling]
|
|
23
|
+
temperature = 0.7
|
|
24
|
+
top_p = 0.95
|
|
25
|
+
min_p = 0.0
|
|
26
|
+
top_k = 0
|
|
27
|
+
|
|
28
|
+
[algorithm]
|
|
29
|
+
name = "grpo"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
model_id = "mlx-community/Qwen3-0.6B-4bit"
|
|
2
|
+
group_size = 4
|
|
3
|
+
max_prompt_len = 512
|
|
4
|
+
max_completion_len = 32
|
|
5
|
+
max_turns = 3
|
|
6
|
+
max_observation_len = 64
|
|
7
|
+
rollout_mode = "parallel_per_turn"
|
|
8
|
+
env_name = "recurring-text"
|
|
9
|
+
steps = 5
|
|
10
|
+
seed = 7
|
|
11
|
+
kl_beta = 0.0
|
|
12
|
+
rank = 8
|
|
13
|
+
scale = 20.0
|
|
14
|
+
dropout = 0.0
|
|
15
|
+
gradient_checkpointing = false
|
|
16
|
+
micro_batch_size = 0
|
|
17
|
+
output = "reference_outputs/gigpo_recurring_reference.npz"
|
|
18
|
+
|
|
19
|
+
[optimizer]
|
|
20
|
+
name = "adam"
|
|
21
|
+
learning_rate = 0.00001
|
|
22
|
+
|
|
23
|
+
[sampling]
|
|
24
|
+
temperature = 0.7
|
|
25
|
+
top_p = 0.95
|
|
26
|
+
min_p = 0.0
|
|
27
|
+
top_k = 0
|
|
28
|
+
|
|
29
|
+
[algorithm]
|
|
30
|
+
name = "gigpo"
|
|
31
|
+
gigpo_omega = 1.0
|
|
32
|
+
gigpo_gamma = 1.0
|
|
33
|
+
gigpo_normalization = "std"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
model_id = "mlx-community/Qwen3-0.6B-4bit"
|
|
2
|
+
quant_bits = 4
|
|
3
|
+
group_size = 2
|
|
4
|
+
max_completion_len = 4
|
|
5
|
+
max_prompt_len = 4096
|
|
6
|
+
steps = 1
|
|
7
|
+
seed = 7
|
|
8
|
+
kl_beta = 0.0
|
|
9
|
+
rank = 8
|
|
10
|
+
scale = 20.0
|
|
11
|
+
dropout = 0.0
|
|
12
|
+
gradient_checkpointing = false
|
|
13
|
+
checkpoint_granularity = "per-layer"
|
|
14
|
+
iogpu_wired_limit_mb = 49152
|
|
15
|
+
micro_batch_size = 0
|
|
16
|
+
output = "/tmp/mlxrl_config_smoke.npz"
|
|
17
|
+
|
|
18
|
+
[optimizer]
|
|
19
|
+
name = "adam"
|
|
20
|
+
learning_rate = 0.00001
|
|
21
|
+
|
|
22
|
+
[sampling]
|
|
23
|
+
temperature = 0.7
|
|
24
|
+
top_p = 0.95
|
|
25
|
+
min_p = 0.0
|
|
26
|
+
top_k = 0
|
|
27
|
+
|
|
28
|
+
[algorithm]
|
|
29
|
+
name = "grpo"
|
|
30
|
+
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
"""Algorithm-specific advantage and loss code."""
|
|
2
2
|
|
|
3
|
+
from mlxrl.algo.gigpo import (
|
|
4
|
+
GiGPOAlgorithm,
|
|
5
|
+
anchor_state_groups,
|
|
6
|
+
anchor_state_step_advantages,
|
|
7
|
+
compute_gigpo_step_advantages,
|
|
8
|
+
flatten_step_records,
|
|
9
|
+
)
|
|
3
10
|
from mlxrl.algo.grpo import (
|
|
4
11
|
AlgorithmLossMetrics,
|
|
5
12
|
DAPOAlgorithm,
|
|
@@ -28,12 +35,17 @@ __all__ = [
|
|
|
28
35
|
"GRPOAlgorithm",
|
|
29
36
|
"GRPOLossMetrics",
|
|
30
37
|
"GSPOAlgorithm",
|
|
38
|
+
"GiGPOAlgorithm",
|
|
31
39
|
"PolicyAlgorithm",
|
|
32
40
|
"RLOOAlgorithm",
|
|
33
41
|
"algorithm_by_name",
|
|
42
|
+
"anchor_state_groups",
|
|
43
|
+
"anchor_state_step_advantages",
|
|
34
44
|
"approximate_kl",
|
|
35
45
|
"clip_ratio",
|
|
46
|
+
"compute_gigpo_step_advantages",
|
|
36
47
|
"filter_zero_advantage_groups",
|
|
48
|
+
"flatten_step_records",
|
|
37
49
|
"group_center_rewards",
|
|
38
50
|
"group_normalize_rewards",
|
|
39
51
|
"grpo_loss",
|