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.
Files changed (88) hide show
  1. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/CHANGELOG.md +21 -0
  2. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/CONTRIBUTING.md +21 -1
  3. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/DESIGN.md +38 -4
  4. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/PKG-INFO +63 -6
  5. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/README.md +62 -5
  6. inductive_mlxrl-0.2.0/examples/qwen35_9b_g2_checkpoint.toml +29 -0
  7. inductive_mlxrl-0.2.0/examples/qwen3_0_6b_gigpo_recurring.toml +33 -0
  8. inductive_mlxrl-0.2.0/examples/qwen3_0_6b_grpo.toml +30 -0
  9. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/__init__.py +1 -2
  10. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/algo/__init__.py +12 -0
  11. inductive_mlxrl-0.2.0/mlxrl/algo/gigpo.py +202 -0
  12. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/algorithm.py +38 -1
  13. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/cli.py +142 -2
  14. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/config.py +48 -6
  15. inductive_mlxrl-0.2.0/mlxrl/env/__init__.py +27 -0
  16. inductive_mlxrl-0.2.0/mlxrl/env/base.py +75 -0
  17. inductive_mlxrl-0.2.0/mlxrl/env/reference.py +104 -0
  18. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/policy/__init__.py +10 -0
  19. inductive_mlxrl-0.2.0/mlxrl/policy/trajectory_logprobs.py +130 -0
  20. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/rollout/__init__.py +10 -0
  21. inductive_mlxrl-0.2.0/mlxrl/rollout/agentic.py +396 -0
  22. inductive_mlxrl-0.2.0/mlxrl/train/__init__.py +18 -0
  23. inductive_mlxrl-0.2.0/mlxrl/train/trajectory.py +276 -0
  24. inductive_mlxrl-0.2.0/mlxrl/trajectory.py +156 -0
  25. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/pyproject.toml +2 -1
  26. inductive_mlxrl-0.2.0/tests/test_agentic_env.py +98 -0
  27. inductive_mlxrl-0.2.0/tests/test_agentic_rollout.py +140 -0
  28. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_cli.py +37 -1
  29. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_config.py +66 -0
  30. inductive_mlxrl-0.2.0/tests/test_gigpo.py +141 -0
  31. inductive_mlxrl-0.2.0/tests/test_train_trajectory.py +117 -0
  32. inductive_mlxrl-0.2.0/tests/test_trajectory_logprobs.py +89 -0
  33. inductive_mlxrl-0.1.2/mlxrl/train/__init__.py +0 -5
  34. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/.gitignore +0 -0
  35. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/LICENSE +0 -0
  36. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/THIRD_PARTY_LICENSES.md +0 -0
  37. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/__init__.py +0 -0
  38. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/audit_phase4_config.py +0 -0
  39. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/external_baseline_worker.py +0 -0
  40. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after.jsonl +0 -0
  41. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after.md +0 -0
  42. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after_final.jsonl +0 -0
  43. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after_final.md +0 -0
  44. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after_noprefixsync.jsonl +0 -0
  45. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_after_noprefixsync.md +0 -0
  46. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_before.jsonl +0 -0
  47. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate1_mlxrl_before.md +0 -0
  48. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate2_unified_smoke.jsonl +0 -0
  49. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate2_unified_smoke.md +0 -0
  50. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_mlx_tune_sync_256.jsonl +0 -0
  51. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_mlx_tune_sync_256.md +0 -0
  52. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_sync_blocked.jsonl +0 -0
  53. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_sync_blocked.md +0 -0
  54. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_sync_smoke.jsonl +0 -0
  55. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate3_sync_smoke.md +0 -0
  56. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate4_config_audit.json +0 -0
  57. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate4_config_audit.md +0 -0
  58. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate4_sampler_smoke.jsonl +0 -0
  59. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate4_sampler_smoke.md +0 -0
  60. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate5_full_reconciled.jsonl +0 -0
  61. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/gate5_full_reconciled.md +0 -0
  62. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_external_smoke.jsonl +0 -0
  63. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_external_smoke.md +0 -0
  64. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_full_all_targets.jsonl +0 -0
  65. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_full_all_targets.md +0 -0
  66. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_local_mlxrl_vs_mlx_lm.jsonl +0 -0
  67. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_local_mlxrl_vs_mlx_lm.md +0 -0
  68. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_mlx_lm_g4_smoke.jsonl +0 -0
  69. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/results/phase4_mlx_lm_g4_smoke.md +0 -0
  70. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/benchmarks/run_phase4.py +0 -0
  71. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/algo/grpo.py +0 -0
  72. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/data/__init__.py +0 -0
  73. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/data/gsm8k.py +0 -0
  74. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/data/rewards.py +0 -0
  75. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/policy/logprobs.py +0 -0
  76. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/policy/model.py +0 -0
  77. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/py.typed +0 -0
  78. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/rollout/naive.py +0 -0
  79. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/rollout/optimized.py +0 -0
  80. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/mlxrl/train/grpo.py +0 -0
  81. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_benchmark_metrics.py +0 -0
  82. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_grpo.py +0 -0
  83. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_import_direction.py +0 -0
  84. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_optimized_rollout.py +0 -0
  85. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_policy_logprobs.py +0 -0
  86. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_policy_model.py +0 -0
  87. {inductive_mlxrl-0.1.2 → inductive_mlxrl-0.2.0}/tests/test_rewards.py +0 -0
  88. {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 template.
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 RLOO. PPO is out of scope because it needs a
11
- critic/value model path, a different forward pass, and a different memory
12
- profile. DPO and ORPO are also out of scope because they are offline preference
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.1.2
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; not a general RL framework, not
29
- preference tuning, and not distributed training.
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. It
36
- reuses `mlx-lm` model loading, LoRA layers, KV caches, and sampling utilities,
37
- and keeps generation and training in one Python process with one model object.
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; not a general RL framework, not
4
- preference tuning, and not distributed training.
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. It
11
- reuses `mlx-lm` model loading, LoRA layers, KV caches, and sampling utilities,
12
- and keeps generation and training in one Python process with one model object.
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
+
@@ -2,5 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.1.0"
6
-
5
+ __version__ = "0.2.0"
@@ -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",