惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
量子位
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
美团技术团队
J
Java Code Geeks
Apple Machine Learning Research
Apple Machine Learning Research
腾讯CDC
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 三生石上(FineUI控件)
IT之家
IT之家
博客园 - 【当耐特】
L
LangChain Blog
A
About on SuperTechFans
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
Netflix TechBlog - Medium
博客园_首页
WordPress大学
WordPress大学
博客园 - Franky
Engineering at Meta
Engineering at Meta
C
Check Point Blog
aimingoo的专栏
aimingoo的专栏
M
MIT News - Artificial intelligence

博客园 - lightsong

LoRA unsloth比transformer库本身的微调有什么优点? Train and Fine-Tune Sentence Transformers Models Symmetric vs. Asymmetric Semantic Search Hierarchical Navigable Small Worlds (HNSW) Vision Transformer + BentoML ML Serving/编排工具 Introducing Gemma 3 270M: The compact model for hyper-efficient AI Utopia -- 企业世界模型 trustgraph semantica semantica vs graphti Industrial-Strength Natural Language Processing seata reference with springboot and other valuable demo outbox pattern with springboot Saga pattern with springboot 基于 Sentence Transformers 的具体应用案例 Vault with Keycloak as workload IAM Ontology Reasoning System ADR Claude Code的hook The AI-Native SDLC playbook Introduction to Dapper Introduction to FluentValidation Introduction to AutoFixture Introduction to FluentAssertions Understanding Return Types: IEnumerable, IReadOnlyCollection, and List Introduction to Refit Introduction to Carter Introduction to Minimal APIs
offline-llms +++ transformer + peft 微调
lightsong · 2026-09-18 · via 博客园 - lightsong

offline-llms 

https://github.com/fanqingsong/offline-llms

ocal RAG + offline LLM demo with Streamlit chat UI, FAISS vector DB, and LoRA tuning/merge utilities — fully powered by Ollama models running locally. Tech Stack: Python 3.11+ · Streamlit · FAISS · Ollama (local LLM + embeddings) · nomic-embed-text · qwen2.5-instruct · PDF ingestion + chunking · LoRA fine-tuning helpers

Offline LLMs Running

This repo contains a local RAG demo, a Streamlit chatbot UI, utilities to build and reuse a FAISS vector DB, and LoRA fine-tuning/merge helpers.

Fine-tuning with LoRA (train.py)

Quick demonstration fine-tune using TRL.

Input data format: data.jsonl lines with keys prompt and response.

{"prompt": "...", "response": "..."}

Run:

Outputs are written to OUT_DIR (default qwen2.5-3b-lora). See code for tunables.


Merge LoRA into a base model (merge.py)

Creates a merged Hugging Face folder you can use directly or export to GGUF.

Examples:

# Merge only
python .\merge.py --base Qwen/Qwen2.5-3B-Instruct --adapter C:\Source\research\qwen2.5-3b-lora --out C:\Source\research\qwen2.5-3b-merged --cpu-only --dtype fp32

# Merge and test generation
python .\merge.py --base Qwen/Qwen2.5-0.5B-Instruct --adapter C:\Source\research\qwen2.5-3b-lora --out C:\Source\research\qwen2.5-3b-merged --cpu-only --dtype fp32 --infer

Web UI for fine-tuning and merging

Click 模型微调与合并 in the top bar of the document chat page.

Fine-tuning

  1. Upload a UTF-8 .jsonl file. Every non-empty line must contain two non-empty strings:
    {"prompt": "Summarize this text", "response": "A short summary"}
  2. Select the dataset and base Hugging Face model, choose a unique output name, and adjust the LoRA/training parameters.
  3. Start the task and follow its live log. Only one train or merge task runs at a time; an active task can be cancelled.

With NVIDIA GPU support, 4-bit QLoRA can be enabled. On CPU it automatically falls back to regular LoRA, which can be very slow for multi-billion parameter models.

Merging

Select a completed adapter, enter the same base model used for training, choose an output name and precision, then start the merge. An optional inference test can run after the merge.

Persistent files are stored under:

  • trainer_data/datasets — uploaded datasets
  • trainer_data/adapters — LoRA adapters
  • trainer_data/merged — merged Hugging Face models
  • trainer_data/jobs — task metadata and logs
  • trainer_data/huggingface — downloaded model cache

Trainer health is available at http://localhost:16006/api/training/health.

transformer + peft微调

from __future__ import annotations

import argparse
import os

import torch
from datasets import load_dataset
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    DataCollatorForLanguageModeling,
    Trainer,
    TrainingArguments,
)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Fine-tune a causal LM with LoRA/QLoRA.")
    parser.add_argument("--base-model", default=os.getenv("BASE_MODEL", "Qwen/Qwen2.5-0.5B-Instruct"))
    parser.add_argument("--data", default=os.getenv("DATA_PATH", "data.jsonl"))
    parser.add_argument("--out", default=os.getenv("OUT_DIR", "qwen2.5-lora"))
    parser.add_argument("--epochs", type=float, default=1.0)
    parser.add_argument("--max-steps", type=int, default=-1)
    parser.add_argument("--batch-size", type=int, default=1)
    parser.add_argument("--gradient-accumulation", type=int, default=4)
    parser.add_argument("--learning-rate", type=float, default=2e-4)
    parser.add_argument("--lora-r", type=int, default=8)
    parser.add_argument("--lora-alpha", type=int, default=16)
    parser.add_argument("--lora-dropout", type=float, default=0.05)
    parser.add_argument("--max-seq-length", type=int, default=1024)
    parser.add_argument("--use-4bit", action="store_true")
    return parser.parse_args()


def positive(value: float | int, name: str) -> None:
    if value <= 0:
        raise SystemExit(f"{name} must be greater than zero")


def main() -> None:
    args = parse_args()
    for value, name in (
        (args.epochs, "epochs"),
        (args.batch_size, "batch-size"),
        (args.gradient_accumulation, "gradient-accumulation"),
        (args.learning_rate, "learning-rate"),
        (args.lora_r, "lora-r"),
        (args.lora_alpha, "lora-alpha"),
        (args.max_seq_length, "max-seq-length"),
    ):
        positive(value, name)
    if not 0 <= args.lora_dropout < 1:
        raise SystemExit("lora-dropout must be in [0, 1)")

    use_cuda = torch.cuda.is_available()
    use_4bit = args.use_4bit and use_cuda
    print(f"Device: {'CUDA - ' + torch.cuda.get_device_name(0) if use_cuda else 'CPU'}")
    if args.use_4bit and not use_cuda:
        print("4-bit QLoRA requested but CUDA is unavailable; using regular LoRA on CPU.")

    quantization = (
        BitsAndBytesConfig(
            load_in_4bit=True,
            bnb_4bit_use_double_quant=True,
            bnb_4bit_quant_type="nf4",
            bnb_4bit_compute_dtype=torch.bfloat16,
        )
        if use_4bit
        else None
    )
    tokenizer = AutoTokenizer.from_pretrained(
        args.base_model, use_fast=True, trust_remote_code=True
    )
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    model = AutoModelForCausalLM.from_pretrained(
        args.base_model,
        quantization_config=quantization,
        low_cpu_mem_usage=True,
        trust_remote_code=True,
        torch_dtype=torch.bfloat16 if use_cuda else torch.float32,
        device_map="auto" if use_4bit else None,
    )
    if use_4bit:
        model = prepare_model_for_kbit_training(model)
    model.config.use_cache = False
    model = get_peft_model(
        model,
        LoraConfig(
            r=args.lora_r,
            lora_alpha=args.lora_alpha,
            lora_dropout=args.lora_dropout,
            bias="none",
            task_type="CAUSAL_LM",
            target_modules=["q_proj", "v_proj"],
        ),
    )
    model.print_trainable_parameters()

    dataset = load_dataset("json", data_files=args.data, split="train")

    def tokenize(samples):
        texts = [
            f"### Instruction:\n{prompt}\n\n### Response:\n{response}{tokenizer.eos_token}"
            for prompt, response in zip(samples["prompt"], samples["response"])
        ]
        return tokenizer(
            texts,
            truncation=True,
            max_length=args.max_seq_length,
            padding=False,
        )

    dataset = dataset.map(tokenize, batched=True, remove_columns=dataset.column_names)
    training_args = TrainingArguments(
        output_dir=args.out,
        num_train_epochs=args.epochs,
        max_steps=args.max_steps,
        per_device_train_batch_size=args.batch_size,
        gradient_accumulation_steps=args.gradient_accumulation,
        learning_rate=args.learning_rate,
        lr_scheduler_type="cosine",
        warmup_ratio=0.1,
        logging_steps=1,
        save_strategy="epoch",
        bf16=use_cuda and torch.cuda.is_bf16_supported(),
        fp16=use_cuda and not torch.cuda.is_bf16_supported(),
        max_grad_norm=0.3,
        gradient_checkpointing=use_cuda,
        optim="paged_adamw_8bit" if use_4bit else "adamw_torch",
        dataloader_pin_memory=use_cuda,
        report_to="none",
    )
    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=dataset,
        data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False),
    )
    trainer.train()
    trainer.model.save_pretrained(args.out)
    tokenizer.save_pretrained(args.out)
    print(f"Adapter saved to {args.out}")


if __name__ == "__main__":
    main()

出处:http://www.cnblogs.com/lightsong/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。