@mlx-node/trl 0.0.2 → 0.0.4

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.
@@ -0,0 +1,33 @@
1
+ export declare class SFTConfigError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export interface SFTTrainerConfig {
5
+ modelName: string;
6
+ outputDir: string;
7
+ runName: string;
8
+ learningRate: number;
9
+ batchSize: number;
10
+ gradientAccumulationSteps: number;
11
+ numEpochs: number;
12
+ maxTrainSamples: number;
13
+ maxGradNorm: number;
14
+ weightDecay: number;
15
+ maxSeqLength: number;
16
+ completionOnly: boolean;
17
+ labelSmoothing: number;
18
+ loggingSteps: number;
19
+ saveSteps: number;
20
+ maxCheckpoints: number;
21
+ logJsonl: boolean;
22
+ tuiMode: boolean;
23
+ gradientCheckpointing: boolean;
24
+ seed: number;
25
+ resumeFromCheckpoint: string;
26
+ }
27
+ declare const DEFAULT_SFT_CONFIG: SFTTrainerConfig;
28
+ export declare function getDefaultSFTConfig(): SFTTrainerConfig;
29
+ export declare function mergeSFTConfig(base: SFTTrainerConfig, update: Partial<SFTTrainerConfig>): SFTTrainerConfig;
30
+ export declare function loadSFTTomlConfig(filePath: string): SFTTrainerConfig;
31
+ export declare function applySFTOverrides(config: SFTTrainerConfig, overrides: string[]): SFTTrainerConfig;
32
+ export { DEFAULT_SFT_CONFIG };
33
+ //# sourceMappingURL=sft-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sft-config.d.ts","sourceRoot":"","sources":["../../src/trainers/sft-config.ts"],"names":[],"mappings":"AAMA,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,gBAAgB;IAE/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAGhB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB,EAAE,MAAM,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IAGpB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IAGvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IAGjB,qBAAqB,EAAE,OAAO,CAAC;IAG/B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,QAAA,MAAM,kBAAkB,EAAE,gBA2BxB,CAAC;AA6GH,wBAAgB,mBAAmB,IAAI,gBAAgB,CAEtD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAa1G;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAsBpE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAoBjG;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,193 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve as resolvePath } from 'node:path';
3
+ import { parse as parseToml } from '@std/toml';
4
+ import { camelCase } from 'change-case';
5
+ export class SFTConfigError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'SFTConfigError';
9
+ }
10
+ }
11
+ const DEFAULT_SFT_CONFIG = Object.freeze({
12
+ modelName: 'Qwen/Qwen3-0.6B',
13
+ outputDir: 'outputs/sft',
14
+ runName: 'sft-run',
15
+ learningRate: 2e-5,
16
+ batchSize: 4,
17
+ gradientAccumulationSteps: 1,
18
+ numEpochs: 3,
19
+ maxTrainSamples: 0,
20
+ maxGradNorm: 1.0,
21
+ weightDecay: 0.01,
22
+ maxSeqLength: 2048,
23
+ completionOnly: false, // Changed to false for TRL parity
24
+ labelSmoothing: 0.0,
25
+ loggingSteps: 10,
26
+ saveSteps: 100,
27
+ maxCheckpoints: 3,
28
+ logJsonl: true,
29
+ tuiMode: false,
30
+ gradientCheckpointing: true,
31
+ seed: 42,
32
+ resumeFromCheckpoint: '',
33
+ });
34
+ const SFT_CONFIG_VALUE_TYPES = {
35
+ modelName: 'string',
36
+ outputDir: 'string',
37
+ runName: 'string',
38
+ learningRate: 'number',
39
+ batchSize: 'number',
40
+ gradientAccumulationSteps: 'number',
41
+ numEpochs: 'number',
42
+ maxTrainSamples: 'number',
43
+ maxGradNorm: 'number',
44
+ weightDecay: 'number',
45
+ maxSeqLength: 'number',
46
+ completionOnly: 'boolean',
47
+ labelSmoothing: 'number',
48
+ loggingSteps: 'number',
49
+ saveSteps: 'number',
50
+ maxCheckpoints: 'number',
51
+ logJsonl: 'boolean',
52
+ tuiMode: 'boolean',
53
+ seed: 'number',
54
+ gradientCheckpointing: 'boolean',
55
+ resumeFromCheckpoint: 'string',
56
+ };
57
+ const SFT_INTEGER_KEYS = new Set([
58
+ 'batchSize',
59
+ 'gradientAccumulationSteps',
60
+ 'numEpochs',
61
+ 'maxTrainSamples',
62
+ 'maxSeqLength',
63
+ 'loggingSteps',
64
+ 'saveSteps',
65
+ 'maxCheckpoints',
66
+ 'seed',
67
+ ]);
68
+ function cloneDefaults() {
69
+ return { ...DEFAULT_SFT_CONFIG };
70
+ }
71
+ function isConfigKey(value) {
72
+ return Object.prototype.hasOwnProperty.call(SFT_CONFIG_VALUE_TYPES, value);
73
+ }
74
+ function coerceBoolean(value, key) {
75
+ if (typeof value === 'boolean')
76
+ return value;
77
+ if (typeof value === 'string') {
78
+ const normalized = value.trim().toLowerCase();
79
+ if (['true', '1', 'yes', 'on'].includes(normalized))
80
+ return true;
81
+ if (['false', '0', 'no', 'off'].includes(normalized))
82
+ return false;
83
+ }
84
+ throw new SFTConfigError(`Invalid boolean for ${key}: ${String(value)}`);
85
+ }
86
+ function coerceNumber(value, key) {
87
+ if (typeof value === 'string' && value.trim() === '') {
88
+ throw new SFTConfigError(`Invalid number for ${key}: empty string`);
89
+ }
90
+ const parsed = typeof value === 'number' ? value : Number(value);
91
+ if (!Number.isFinite(parsed)) {
92
+ throw new SFTConfigError(`Invalid number for ${key}: ${String(value)}`);
93
+ }
94
+ if (SFT_INTEGER_KEYS.has(key) && !Number.isInteger(parsed)) {
95
+ throw new SFTConfigError(`Expected integer for ${key}, received ${parsed}`);
96
+ }
97
+ return parsed;
98
+ }
99
+ function coerceString(value, key) {
100
+ if (typeof value === 'string')
101
+ return value;
102
+ throw new SFTConfigError(`Invalid string for ${key}: ${String(value)}`);
103
+ }
104
+ function coerceValue(key, value) {
105
+ const expected = SFT_CONFIG_VALUE_TYPES[key];
106
+ if (expected === 'boolean') {
107
+ return coerceBoolean(value, key);
108
+ }
109
+ if (expected === 'number') {
110
+ return coerceNumber(value, key);
111
+ }
112
+ return coerceString(value, key);
113
+ }
114
+ function setConfigValue(config, key, value) {
115
+ config[key] = value;
116
+ }
117
+ function normalizeTomlRecord(record) {
118
+ const normalized = {};
119
+ for (const [rawKey, rawValue] of Object.entries(record)) {
120
+ // TOML uses snake_case, config uses camelCase
121
+ const key = camelCase(rawKey);
122
+ if (!isConfigKey(key)) {
123
+ continue;
124
+ }
125
+ setConfigValue(normalized, key, coerceValue(key, rawValue));
126
+ }
127
+ return normalized;
128
+ }
129
+ export function getDefaultSFTConfig() {
130
+ return cloneDefaults();
131
+ }
132
+ export function mergeSFTConfig(base, update) {
133
+ if (!update) {
134
+ return { ...base };
135
+ }
136
+ const result = { ...base };
137
+ for (const [key, value] of Object.entries(update)) {
138
+ if (value === undefined)
139
+ continue;
140
+ if (!isConfigKey(key)) {
141
+ throw new SFTConfigError(`Unknown configuration key: ${key}`);
142
+ }
143
+ setConfigValue(result, key, value);
144
+ }
145
+ return result;
146
+ }
147
+ export function loadSFTTomlConfig(filePath) {
148
+ const absolutePath = resolvePath(filePath);
149
+ let fileContents;
150
+ try {
151
+ fileContents = readFileSync(absolutePath, 'utf8');
152
+ }
153
+ catch (error) {
154
+ const message = error instanceof Error ? error.message : String(error);
155
+ throw new SFTConfigError(`Failed to read config at ${absolutePath}: ${message}`);
156
+ }
157
+ let parsedRaw;
158
+ try {
159
+ parsedRaw = parseToml(fileContents);
160
+ }
161
+ catch (error) {
162
+ const message = error instanceof Error ? error.message : String(error);
163
+ throw new SFTConfigError(`Failed to parse TOML at ${absolutePath}: ${message}`);
164
+ }
165
+ if (parsedRaw === null || typeof parsedRaw !== 'object' || Array.isArray(parsedRaw)) {
166
+ throw new SFTConfigError(`Expected table at ${absolutePath}`);
167
+ }
168
+ const parsed = parsedRaw;
169
+ const normalized = normalizeTomlRecord(parsed);
170
+ return mergeSFTConfig(getDefaultSFTConfig(), normalized);
171
+ }
172
+ export function applySFTOverrides(config, overrides) {
173
+ if (!overrides.length) {
174
+ return { ...config };
175
+ }
176
+ const accumulated = {};
177
+ for (const entry of overrides) {
178
+ const idx = entry.indexOf('=');
179
+ if (idx === -1) {
180
+ throw new SFTConfigError(`Invalid override "${entry}", expected key=value format`);
181
+ }
182
+ const rawKey = entry.slice(0, idx).trim();
183
+ const rawValue = entry.slice(idx + 1).trim();
184
+ // Accept both snake_case and camelCase overrides
185
+ const key = camelCase(rawKey);
186
+ if (!isConfigKey(key)) {
187
+ throw new SFTConfigError(`Unknown configuration key in override: ${rawKey}`);
188
+ }
189
+ setConfigValue(accumulated, key, coerceValue(key, rawValue));
190
+ }
191
+ return mergeSFTConfig(config, accumulated);
192
+ }
193
+ export { DEFAULT_SFT_CONFIG };
@@ -0,0 +1,148 @@
1
+ /**
2
+ * SFT (Supervised Fine-Tuning) Trainer
3
+ *
4
+ * This module provides a Rust-native SFT training engine for training
5
+ * models on fixed prompt-completion pairs using cross-entropy loss.
6
+ *
7
+ * ## Key Features
8
+ * - Training loop runs in Rust (eliminates FFI overhead)
9
+ * - Cross-entropy loss with completion masking (ignore_index=-100)
10
+ * - Label smoothing support
11
+ * - Gradient accumulation and clipping
12
+ * - High-level train() method for full training runs
13
+ * - Low-level trainStep() for custom training loops
14
+ *
15
+ * ## Usage
16
+ * ```typescript
17
+ * const trainer = await SFTTrainer.create({
18
+ * modelPath: './model',
19
+ * learningRate: 2e-5,
20
+ * numEpochs: 3,
21
+ * });
22
+ * await trainer.train(dataset);
23
+ * ```
24
+ */
25
+ import { Qwen3Tokenizer, type SftStepMetrics } from '@mlx-node/core';
26
+ import { type TrainableModel } from '@mlx-node/lm';
27
+ import { SFTDataset, type SFTBatch } from '../data/sft-dataset.js';
28
+ import type { SFTTrainerConfig } from './sft-config.js';
29
+ import { type TrainingLogger } from './training-logger.js';
30
+ export { SftTrainingEngine } from '@mlx-node/core';
31
+ export type { SftEngineConfig, SftStepMetrics, SftEpochMetrics } from '@mlx-node/core';
32
+ /**
33
+ * Training state saved with checkpoints for resumption
34
+ */
35
+ export interface SFTTrainingState {
36
+ step: number;
37
+ epoch: number;
38
+ timestamp: string;
39
+ trainerType: 'sft';
40
+ }
41
+ /**
42
+ * Training step result
43
+ */
44
+ export interface SFTTrainStepResult {
45
+ /** Step metrics */
46
+ metrics: SftStepMetrics;
47
+ /** Current epoch */
48
+ epoch: number;
49
+ }
50
+ /**
51
+ * SFT Trainer - Rust-Native Training Engine
52
+ *
53
+ * Provides a TypeScript-friendly interface to the Rust SFT training engine.
54
+ */
55
+ export declare class SFTTrainer {
56
+ private engine;
57
+ private model;
58
+ private tokenizer;
59
+ private config;
60
+ private currentEpoch;
61
+ private currentStep;
62
+ /** Original model path (for tokenizer files when saving checkpoints) */
63
+ private originalModelPath?;
64
+ private paused;
65
+ private stopRequested;
66
+ private stdinInterface?;
67
+ private logger;
68
+ private sampleDisplayMode;
69
+ private signalHandlersInstalled;
70
+ /**
71
+ * Create a new SFT trainer from a model
72
+ *
73
+ * @param model - Pre-loaded Qwen3 model
74
+ * @param tokenizer - Pre-loaded tokenizer
75
+ * @param config - Training configuration
76
+ * @param logger - Optional custom logger
77
+ */
78
+ constructor(model: TrainableModel, tokenizer: Qwen3Tokenizer, config?: Partial<SFTTrainerConfig>, logger?: TrainingLogger);
79
+ /**
80
+ * Setup OS signal handlers for graceful shutdown on interrupt.
81
+ * Saves an emergency checkpoint before exiting to prevent progress loss.
82
+ */
83
+ private setupSignalHandlers;
84
+ /**
85
+ * Setup stdin handler for TUI control commands
86
+ */
87
+ private setupStdinHandler;
88
+ /**
89
+ * Handle a command received from stdin
90
+ */
91
+ private handleStdinCommand;
92
+ /**
93
+ * Wait for resume if paused
94
+ */
95
+ private waitForResume;
96
+ /**
97
+ * Create a trainer by loading a model from disk
98
+ *
99
+ * @param config - Configuration including modelPath
100
+ * @returns Promise<SFTTrainer>
101
+ */
102
+ static create(config: Partial<SFTTrainerConfig>): Promise<SFTTrainer>;
103
+ /**
104
+ * Find the latest checkpoint in the output directory
105
+ */
106
+ static findLatestCheckpoint(outputDir?: string): string | null;
107
+ /**
108
+ * Run a single training step
109
+ *
110
+ * @param batch - Tokenized batch with input_ids and labels
111
+ * @returns Training step metrics
112
+ */
113
+ trainStep(batch: SFTBatch): Promise<SFTTrainStepResult>;
114
+ /**
115
+ * Run a full training loop over a dataset
116
+ *
117
+ * @param dataset - SFT dataset or path to JSONL file
118
+ */
119
+ train(dataset: SFTDataset | string): Promise<void>;
120
+ /**
121
+ * Save a checkpoint with model weights and training state
122
+ *
123
+ * @param name - Checkpoint name (default: "checkpoint-{step}")
124
+ * @returns Path to saved checkpoint
125
+ */
126
+ saveCheckpoint(name?: string): Promise<string>;
127
+ /**
128
+ * Remove old checkpoints, keeping only the most recent ones
129
+ */
130
+ private cleanupOldCheckpoints;
131
+ /**
132
+ * Get current training step
133
+ */
134
+ get step(): number;
135
+ /**
136
+ * Get current epoch
137
+ */
138
+ get epoch(): number;
139
+ /**
140
+ * Get the underlying model for inference
141
+ */
142
+ getModel(): TrainableModel;
143
+ /**
144
+ * Get the tokenizer
145
+ */
146
+ getTokenizer(): Qwen3Tokenizer;
147
+ }
148
+ //# sourceMappingURL=sft-trainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sft-trainer.d.ts","sourceRoot":"","sources":["../../src/trainers/sft-trainer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,EAKL,cAAc,EAGd,KAAK,cAAc,EAEpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAkB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,KAAK,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,mBAAmB;IACnB,OAAO,EAAE,cAAc,CAAC;IACxB,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,WAAW,CAAa;IAChC,wEAAwE;IACxE,OAAO,CAAC,iBAAiB,CAAC,CAAS;IAGnC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,cAAc,CAAC,CAAqB;IAC5C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,iBAAiB,CAA0C;IACnE,OAAO,CAAC,uBAAuB,CAAkB;IAEjD;;;;;;;OAOG;gBAED,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,cAAc,EACzB,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM,EACtC,MAAM,CAAC,EAAE,cAAc;IAiDzB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA8B3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmC1B;;OAEG;YACW,aAAa;IAM3B;;;;;OAKG;WACU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IAgE3E;;OAEG;IACH,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAmB9D;;;;;OAKG;IACG,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAqB7D;;;;OAIG;IACG,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiLxD;;;;;OAKG;IACG,cAAc,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA+CpD;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAiC7B;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAElB;IAED;;OAEG;IACH,QAAQ,IAAI,cAAc;IAU1B;;OAEG;IACH,YAAY,IAAI,cAAc;CAG/B"}