Skip to content

Commit 7b8afc9

Browse files
committed
feat(experimental): add fine-tuning endpoint and TRL support
This changeset defines new GRPC signatues for Fine tuning backends, and add TRL backend as initial fine-tuning engine. This implementation also supports exporting to GGUF and automatically importing it to LocalAI after fine-tuning. Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent ae4b758 commit 7b8afc9

28 files changed

Lines changed: 1096 additions & 56 deletions

File tree

.github/workflows/backend.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ jobs:
118118
dockerfile: "./backend/Dockerfile.python"
119119
context: "./"
120120
ubuntu-version: '2404'
121+
- build-type: ''
122+
cuda-major-version: ""
123+
cuda-minor-version: ""
124+
platforms: 'linux/amd64'
125+
tag-latest: 'auto'
126+
tag-suffix: '-cpu-trl'
127+
runs-on: 'ubuntu-latest'
128+
base-image: "ubuntu:24.04"
129+
skip-drivers: 'true'
130+
backend: "trl"
131+
dockerfile: "./backend/Dockerfile.python"
132+
context: "./"
133+
ubuntu-version: '2404'
121134
- build-type: ''
122135
cuda-major-version: ""
123136
cuda-minor-version: ""
@@ -366,6 +379,19 @@ jobs:
366379
dockerfile: "./backend/Dockerfile.python"
367380
context: "./"
368381
ubuntu-version: '2404'
382+
- build-type: 'cublas'
383+
cuda-major-version: "12"
384+
cuda-minor-version: "8"
385+
platforms: 'linux/amd64'
386+
tag-latest: 'auto'
387+
tag-suffix: '-gpu-nvidia-cuda-12-trl'
388+
runs-on: 'ubuntu-latest'
389+
base-image: "ubuntu:24.04"
390+
skip-drivers: 'false'
391+
backend: "trl"
392+
dockerfile: "./backend/Dockerfile.python"
393+
context: "./"
394+
ubuntu-version: '2404'
369395
- build-type: 'cublas'
370396
cuda-major-version: "12"
371397
cuda-minor-version: "8"
@@ -757,6 +783,19 @@ jobs:
757783
dockerfile: "./backend/Dockerfile.python"
758784
context: "./"
759785
ubuntu-version: '2404'
786+
- build-type: 'cublas'
787+
cuda-major-version: "13"
788+
cuda-minor-version: "0"
789+
platforms: 'linux/amd64'
790+
tag-latest: 'auto'
791+
tag-suffix: '-gpu-nvidia-cuda-13-trl'
792+
runs-on: 'ubuntu-latest'
793+
base-image: "ubuntu:24.04"
794+
skip-drivers: 'false'
795+
backend: "trl"
796+
dockerfile: "./backend/Dockerfile.python"
797+
context: "./"
798+
ubuntu-version: '2404'
760799
- build-type: 'l4t'
761800
cuda-major-version: "13"
762801
cuda-minor-version: "0"

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This file is an index to detailed topic guides in the `.agents/` directory. Read
1212
| [.agents/llama-cpp-backend.md](.agents/llama-cpp-backend.md) | Working on the llama.cpp backend — architecture, updating, tool call parsing |
1313
| [.agents/testing-mcp-apps.md](.agents/testing-mcp-apps.md) | Testing MCP Apps (interactive tool UIs) in the React UI |
1414
| [.agents/api-endpoints-and-auth.md](.agents/api-endpoints-and-auth.md) | Adding API endpoints, auth middleware, feature permissions, user access control |
15+
| [.agents/debugging-backends.md](.agents/debugging-backends.md) | Debugging runtime backend failures, dependency conflicts, rebuilding backends |
1516

1617
## Quick Reference
1718

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Disable parallel execution for backend builds
2-
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/outetts backends/piper backends/stablediffusion-ggml backends/whisper backends/faster-whisper backends/silero-vad backends/local-store backends/huggingface backends/rfdetr backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/mlx-distributed backends/stablediffusion-ggml-darwin backends/vllm backends/vllm-omni backends/moonshine backends/pocket-tts backends/qwen-tts backends/faster-qwen3-tts backends/qwen-asr backends/nemo backends/voxcpm backends/whisperx backends/ace-step backends/acestep-cpp backends/fish-speech backends/voxtral backends/opus
2+
.NOTPARALLEL: backends/diffusers backends/llama-cpp backends/outetts backends/piper backends/stablediffusion-ggml backends/whisper backends/faster-whisper backends/silero-vad backends/local-store backends/huggingface backends/rfdetr backends/kitten-tts backends/kokoro backends/chatterbox backends/llama-cpp-darwin backends/neutts build-darwin-python-backend build-darwin-go-backend backends/mlx backends/diffuser-darwin backends/mlx-vlm backends/mlx-audio backends/mlx-distributed backends/stablediffusion-ggml-darwin backends/vllm backends/vllm-omni backends/moonshine backends/pocket-tts backends/qwen-tts backends/faster-qwen3-tts backends/qwen-asr backends/nemo backends/voxcpm backends/whisperx backends/ace-step backends/acestep-cpp backends/fish-speech backends/voxtral backends/opus backends/trl
33

44
GOCMD=go
55
GOTEST=$(GOCMD) test
@@ -421,6 +421,7 @@ prepare-test-extra: protogen-python
421421
$(MAKE) -C backend/python/voxcpm
422422
$(MAKE) -C backend/python/whisperx
423423
$(MAKE) -C backend/python/ace-step
424+
$(MAKE) -C backend/python/trl
424425

425426
test-extra: prepare-test-extra
426427
$(MAKE) -C backend/python/transformers test
@@ -440,6 +441,7 @@ test-extra: prepare-test-extra
440441
$(MAKE) -C backend/python/voxcpm test
441442
$(MAKE) -C backend/python/whisperx test
442443
$(MAKE) -C backend/python/ace-step test
444+
$(MAKE) -C backend/python/trl test
443445

444446
DOCKER_IMAGE?=local-ai
445447
IMAGE_TYPE?=core
@@ -572,6 +574,7 @@ BACKEND_VOXCPM = voxcpm|python|.|false|true
572574
BACKEND_WHISPERX = whisperx|python|.|false|true
573575
BACKEND_ACE_STEP = ace-step|python|.|false|true
574576
BACKEND_MLX_DISTRIBUTED = mlx-distributed|python|./|false|true
577+
BACKEND_TRL = trl|python|.|false|true
575578

576579
# Helper function to build docker image for a backend
577580
# Usage: $(call docker-build-backend,BACKEND_NAME,DOCKERFILE_TYPE,BUILD_CONTEXT,PROGRESS_FLAG,NEEDS_BACKEND_ARG)
@@ -629,12 +632,13 @@ $(eval $(call generate-docker-build-target,$(BACKEND_WHISPERX)))
629632
$(eval $(call generate-docker-build-target,$(BACKEND_ACE_STEP)))
630633
$(eval $(call generate-docker-build-target,$(BACKEND_ACESTEP_CPP)))
631634
$(eval $(call generate-docker-build-target,$(BACKEND_MLX_DISTRIBUTED)))
635+
$(eval $(call generate-docker-build-target,$(BACKEND_TRL)))
632636

633637
# Pattern rule for docker-save targets
634638
docker-save-%: backend-images
635639
docker save local-ai-backend:$* -o backend-images/$*.tar
636640

637-
docker-build-backends: docker-build-llama-cpp docker-build-rerankers docker-build-vllm docker-build-vllm-omni docker-build-transformers docker-build-outetts docker-build-diffusers docker-build-kokoro docker-build-faster-whisper docker-build-coqui docker-build-chatterbox docker-build-vibevoice docker-build-moonshine docker-build-pocket-tts docker-build-qwen-tts docker-build-fish-speech docker-build-faster-qwen3-tts docker-build-qwen-asr docker-build-nemo docker-build-voxcpm docker-build-whisperx docker-build-ace-step docker-build-acestep-cpp docker-build-voxtral docker-build-mlx-distributed
641+
docker-build-backends: docker-build-llama-cpp docker-build-rerankers docker-build-vllm docker-build-vllm-omni docker-build-transformers docker-build-outetts docker-build-diffusers docker-build-kokoro docker-build-faster-whisper docker-build-coqui docker-build-chatterbox docker-build-vibevoice docker-build-moonshine docker-build-pocket-tts docker-build-qwen-tts docker-build-fish-speech docker-build-faster-qwen3-tts docker-build-qwen-asr docker-build-nemo docker-build-voxcpm docker-build-whisperx docker-build-ace-step docker-build-acestep-cpp docker-build-voxtral docker-build-mlx-distributed docker-build-trl
638642

639643
########################################################
640644
### Mock Backend for E2E Tests

backend/backend.proto

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ service Backend {
3939
rpc AudioDecode(AudioDecodeRequest) returns (AudioDecodeResult) {}
4040

4141
rpc ModelMetadata(ModelOptions) returns (ModelMetadataResponse) {}
42+
43+
// Fine-tuning RPCs
44+
rpc StartFineTune(FineTuneRequest) returns (FineTuneJobResult) {}
45+
rpc FineTuneProgress(FineTuneProgressRequest) returns (stream FineTuneProgressUpdate) {}
46+
rpc StopFineTune(FineTuneStopRequest) returns (Result) {}
47+
rpc ListCheckpoints(ListCheckpointsRequest) returns (ListCheckpointsResponse) {}
48+
rpc ExportModel(ExportModelRequest) returns (Result) {}
4249
}
4350

4451
// Define the empty request
@@ -528,3 +535,105 @@ message ModelMetadataResponse {
528535
string rendered_template = 2; // The rendered chat template with enable_thinking=true (empty if not applicable)
529536
ToolFormatMarkers tool_format = 3; // Auto-detected tool format markers from differential template analysis
530537
}
538+
539+
// Fine-tuning messages
540+
541+
message FineTuneRequest {
542+
// Model identification
543+
string model = 1; // HF model name or local path
544+
string training_type = 2; // "lora", "loha", "lokr", "full" — what parameters to train
545+
string training_method = 3; // "sft", "dpo", "grpo", "rloo", "reward", "kto", "orpo", "network_training"
546+
547+
// Adapter config (universal across LoRA/LoHa/LoKr for LLM + diffusion)
548+
int32 adapter_rank = 10; // LoRA rank (r), default 16
549+
int32 adapter_alpha = 11; // scaling factor, default 16
550+
float adapter_dropout = 12; // default 0.0
551+
repeated string target_modules = 13; // layer names to adapt
552+
553+
// Universal training hyperparameters
554+
float learning_rate = 20; // default 2e-4
555+
int32 num_epochs = 21; // default 3
556+
int32 batch_size = 22; // default 2
557+
int32 gradient_accumulation_steps = 23; // default 4
558+
int32 warmup_steps = 24; // default 5
559+
int32 max_steps = 25; // 0 = use epochs
560+
int32 save_steps = 26; // 0 = only save final
561+
float weight_decay = 27; // default 0.01
562+
bool gradient_checkpointing = 28;
563+
string optimizer = 29; // adamw_8bit, adamw, sgd, adafactor, prodigy
564+
int32 seed = 30; // default 3407
565+
string mixed_precision = 31; // fp16, bf16, fp8, no
566+
567+
// Dataset
568+
string dataset_source = 40; // HF dataset ID, local file/dir path
569+
string dataset_split = 41; // train, test, etc.
570+
571+
// Output
572+
string output_dir = 50;
573+
string job_id = 51; // client-assigned or auto-generated
574+
575+
// Resume training from a checkpoint
576+
string resume_from_checkpoint = 55; // path to checkpoint dir to resume from
577+
578+
// Backend-specific AND method-specific extensibility
579+
map<string, string> extra_options = 60;
580+
}
581+
582+
message FineTuneJobResult {
583+
string job_id = 1;
584+
bool success = 2;
585+
string message = 3;
586+
}
587+
588+
message FineTuneProgressRequest {
589+
string job_id = 1;
590+
}
591+
592+
message FineTuneProgressUpdate {
593+
string job_id = 1;
594+
int32 current_step = 2;
595+
int32 total_steps = 3;
596+
float current_epoch = 4;
597+
float total_epochs = 5;
598+
float loss = 6;
599+
float learning_rate = 7;
600+
float grad_norm = 8;
601+
float eval_loss = 9;
602+
float eta_seconds = 10;
603+
float progress_percent = 11;
604+
string status = 12; // queued, caching, loading_model, loading_dataset, training, saving, completed, failed, stopped
605+
string message = 13;
606+
string checkpoint_path = 14; // set when a checkpoint is saved
607+
string sample_path = 15; // set when a sample is generated (video/image backends)
608+
map<string, float> extra_metrics = 16; // method-specific metrics
609+
}
610+
611+
message FineTuneStopRequest {
612+
string job_id = 1;
613+
bool save_checkpoint = 2;
614+
}
615+
616+
message ListCheckpointsRequest {
617+
string output_dir = 1;
618+
}
619+
620+
message ListCheckpointsResponse {
621+
repeated CheckpointInfo checkpoints = 1;
622+
}
623+
624+
message CheckpointInfo {
625+
string path = 1;
626+
int32 step = 2;
627+
float epoch = 3;
628+
float loss = 4;
629+
string created_at = 5;
630+
}
631+
632+
message ExportModelRequest {
633+
string checkpoint_path = 1;
634+
string output_path = 2;
635+
string export_format = 3; // lora, loha, lokr, merged_16bit, merged_4bit, gguf, diffusers
636+
string quantization_method = 4; // for GGUF: q4_k_m, q5_k_m, q8_0, f16, etc.
637+
string model = 5; // base model name (for merge operations)
638+
map<string, string> extra_options = 6;
639+
}

backend/index.yaml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3029,3 +3029,54 @@
30293029
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-voxtral"
30303030
mirrors:
30313031
- localai/localai-backends:master-metal-darwin-arm64-voxtral
3032+
- &trl
3033+
name: "trl"
3034+
alias: "trl"
3035+
license: apache-2.0
3036+
description: |
3037+
HuggingFace TRL fine-tuning backend. Supports SFT, DPO, GRPO, RLOO, Reward, KTO, ORPO training methods.
3038+
Works on CPU and GPU.
3039+
urls:
3040+
- https://github.com/huggingface/trl
3041+
tags:
3042+
- fine-tuning
3043+
- LLM
3044+
- CPU
3045+
- GPU
3046+
- CUDA
3047+
capabilities:
3048+
default: "cpu-trl"
3049+
nvidia: "cuda12-trl"
3050+
nvidia-cuda-12: "cuda12-trl"
3051+
nvidia-cuda-13: "cuda13-trl"
3052+
## TRL backend images
3053+
- !!merge <<: *trl
3054+
name: "cpu-trl"
3055+
uri: "quay.io/go-skynet/local-ai-backends:latest-cpu-trl"
3056+
mirrors:
3057+
- localai/localai-backends:latest-cpu-trl
3058+
- !!merge <<: *trl
3059+
name: "cpu-trl-development"
3060+
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-trl"
3061+
mirrors:
3062+
- localai/localai-backends:master-cpu-trl
3063+
- !!merge <<: *trl
3064+
name: "cuda12-trl"
3065+
uri: "quay.io/go-skynet/local-ai-backends:latest-cublas-cuda12-trl"
3066+
mirrors:
3067+
- localai/localai-backends:latest-cublas-cuda12-trl
3068+
- !!merge <<: *trl
3069+
name: "cuda12-trl-development"
3070+
uri: "quay.io/go-skynet/local-ai-backends:master-cublas-cuda12-trl"
3071+
mirrors:
3072+
- localai/localai-backends:master-cublas-cuda12-trl
3073+
- !!merge <<: *trl
3074+
name: "cuda13-trl"
3075+
uri: "quay.io/go-skynet/local-ai-backends:latest-cublas-cuda13-trl"
3076+
mirrors:
3077+
- localai/localai-backends:latest-cublas-cuda13-trl
3078+
- !!merge <<: *trl
3079+
name: "cuda13-trl-development"
3080+
uri: "quay.io/go-skynet/local-ai-backends:master-cublas-cuda13-trl"
3081+
mirrors:
3082+
- localai/localai-backends:master-cublas-cuda13-trl

core/cli/run.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ type RunCMD struct {
121121
AgentPoolCollectionDBPath string `env:"LOCALAI_AGENT_POOL_COLLECTION_DB_PATH" help:"Database path for agent collections" group:"agents"`
122122
AgentHubURL string `env:"LOCALAI_AGENT_HUB_URL" default:"https://agenthub.localai.io" help:"URL for the agent hub where users can browse and download agent configurations" group:"agents"`
123123

124+
// Fine-tuning
125+
EnableFineTuning bool `env:"LOCALAI_ENABLE_FINETUNING" default:"false" help:"Enable fine-tuning support" group:"finetuning"`
126+
124127
// Authentication
125128
AuthEnabled bool `env:"LOCALAI_AUTH" default:"false" help:"Enable user authentication and authorization" group:"auth"`
126129
AuthDatabaseURL string `env:"LOCALAI_AUTH_DATABASE_URL,DATABASE_URL" help:"Database URL for auth (postgres:// or file path for SQLite). Defaults to {DataPath}/database.db" group:"auth"`
@@ -326,6 +329,11 @@ func (r *RunCMD) Run(ctx *cliContext.Context) error {
326329
opts = append(opts, config.WithAgentHubURL(r.AgentHubURL))
327330
}
328331

332+
// Fine-tuning
333+
if r.EnableFineTuning {
334+
opts = append(opts, config.EnableFineTuning)
335+
}
336+
329337
// Authentication
330338
authEnabled := r.AuthEnabled || r.GitHubClientID != "" || r.OIDCClientID != ""
331339
if authEnabled {

core/config/application_config.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ type ApplicationConfig struct {
9797
// Agent Pool (LocalAGI integration)
9898
AgentPool AgentPoolConfig
9999

100+
// Fine-tuning
101+
FineTuning FineTuningConfig
102+
100103
// Authentication & Authorization
101104
Auth AuthConfig
102105
}
@@ -142,6 +145,11 @@ type AgentPoolConfig struct {
142145
AgentHubURL string // default: "https://agenthub.localai.io"
143146
}
144147

148+
// FineTuningConfig holds configuration for fine-tuning support.
149+
type FineTuningConfig struct {
150+
Enabled bool
151+
}
152+
145153
type AppOption func(*ApplicationConfig)
146154

147155
func NewApplicationConfig(o ...AppOption) *ApplicationConfig {
@@ -733,6 +741,12 @@ func WithAgentHubURL(url string) AppOption {
733741
}
734742
}
735743

744+
// Fine-tuning options
745+
746+
var EnableFineTuning = func(o *ApplicationConfig) {
747+
o.FineTuning.Enabled = true
748+
}
749+
736750
// Auth options
737751

738752
func WithAuthEnabled(enabled bool) AppOption {

core/http/app.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,17 @@ func API(application *application.Application) (*echo.Echo, error) {
302302
mcpMw := auth.RequireFeature(application.AuthDB(), auth.FeatureMCP)
303303
routes.RegisterLocalAIRoutes(e, requestExtractor, application.ModelConfigLoader(), application.ModelLoader(), application.ApplicationConfig(), application.GalleryService(), opcache, application.TemplatesEvaluator(), application, adminMiddleware, mcpJobsMw, mcpMw)
304304
routes.RegisterAgentPoolRoutes(e, application, agentsMw, skillsMw, collectionsMw)
305+
// Fine-tuning routes
306+
if application.ApplicationConfig().FineTuning.Enabled {
307+
fineTuningMw := auth.RequireFeature(application.AuthDB(), auth.FeatureFineTuning)
308+
ftService := services.NewFineTuneService(
309+
application.ApplicationConfig(),
310+
application.ModelLoader(),
311+
application.ModelConfigLoader(),
312+
)
313+
routes.RegisterFineTuningRoutes(e, ftService, application.ApplicationConfig(), fineTuningMw)
314+
}
315+
305316
routes.RegisterOpenAIRoutes(e, requestExtractor, application)
306317
routes.RegisterAnthropicRoutes(e, requestExtractor, application)
307318
routes.RegisterOpenResponsesRoutes(e, requestExtractor, application)

core/http/auth/features.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ var RouteFeatureRegistry = []RouteFeature{
8585
{"POST", "/stores/delete", FeatureStores},
8686
{"POST", "/stores/get", FeatureStores},
8787
{"POST", "/stores/find", FeatureStores},
88+
89+
// Fine-tuning
90+
{"POST", "/api/fine-tuning/jobs", FeatureFineTuning},
91+
{"GET", "/api/fine-tuning/jobs", FeatureFineTuning},
92+
{"GET", "/api/fine-tuning/jobs/:id", FeatureFineTuning},
93+
{"POST", "/api/fine-tuning/jobs/:id/stop", FeatureFineTuning},
94+
{"DELETE", "/api/fine-tuning/jobs/:id", FeatureFineTuning},
95+
{"GET", "/api/fine-tuning/jobs/:id/progress", FeatureFineTuning},
96+
{"GET", "/api/fine-tuning/jobs/:id/checkpoints", FeatureFineTuning},
97+
{"POST", "/api/fine-tuning/jobs/:id/export", FeatureFineTuning},
98+
{"GET", "/api/fine-tuning/jobs/:id/download", FeatureFineTuning},
99+
{"POST", "/api/fine-tuning/datasets", FeatureFineTuning},
88100
}
89101

90102
// FeatureMeta describes a feature for the admin API/UI.
@@ -101,6 +113,7 @@ func AgentFeatureMetas() []FeatureMeta {
101113
{FeatureSkills, "Skills", false},
102114
{FeatureCollections, "Collections", false},
103115
{FeatureMCPJobs, "MCP CI Jobs", false},
116+
{FeatureFineTuning, "Fine-Tuning", false},
104117
}
105118
}
106119

0 commit comments

Comments
 (0)