Santosh0322 commited on
Commit
4e046d9
·
verified ·
1 Parent(s): eccdde4

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. README.md +966 -0
  3. requirements.txt +141 -0
  4. scripts/api_example/test_image.py +65 -0
  5. scripts/api_example/test_toolcall.py +77 -0
  6. scripts/bench_qwen.py +173 -0
  7. scripts/convert_ckpt/llamafy_baichuan2.py +112 -0
  8. scripts/convert_ckpt/llamafy_qwen.py +165 -0
  9. scripts/convert_ckpt/tiny_llama4.py +39 -0
  10. scripts/convert_ckpt/tiny_qwen3.py +32 -0
  11. scripts/dcp2hf.py +76 -0
  12. scripts/eval_bleu_rouge.py +79 -0
  13. scripts/hf2dcp.py +63 -0
  14. scripts/llama_pro.py +129 -0
  15. scripts/loftq_init.py +88 -0
  16. scripts/megatron_merge.py +130 -0
  17. scripts/pissa_init.py +86 -0
  18. scripts/qwen_omni_merge.py +140 -0
  19. scripts/stat_utils/cal_flops.py +49 -0
  20. scripts/stat_utils/cal_lr.py +98 -0
  21. scripts/stat_utils/cal_mfu.py +161 -0
  22. scripts/stat_utils/cal_ppl.py +134 -0
  23. scripts/stat_utils/length_cdf.py +69 -0
  24. scripts/vllm_infer.py +280 -0
  25. src/api.py +33 -0
  26. src/llamafactory/__init__.py +31 -0
  27. src/llamafactory/__pycache__/__init__.cpython-312.pyc +0 -0
  28. src/llamafactory/__pycache__/cli.cpython-312.pyc +0 -0
  29. src/llamafactory/__pycache__/launcher.cpython-312.pyc +0 -0
  30. src/llamafactory/api/__init__.py +0 -0
  31. src/llamafactory/api/app.py +133 -0
  32. src/llamafactory/api/chat.py +294 -0
  33. src/llamafactory/api/common.py +96 -0
  34. src/llamafactory/api/protocol.py +156 -0
  35. src/llamafactory/chat/__init__.py +19 -0
  36. src/llamafactory/chat/base_engine.py +98 -0
  37. src/llamafactory/chat/chat_model.py +200 -0
  38. src/llamafactory/chat/hf_engine.py +423 -0
  39. src/llamafactory/chat/sglang_engine.py +292 -0
  40. src/llamafactory/chat/vllm_engine.py +273 -0
  41. src/llamafactory/cli.py +31 -0
  42. src/llamafactory/data/__init__.py +37 -0
  43. src/llamafactory/data/__pycache__/__init__.cpython-312.pyc +0 -0
  44. src/llamafactory/data/__pycache__/collator.cpython-312.pyc +0 -0
  45. src/llamafactory/data/__pycache__/converter.cpython-312.pyc +0 -0
  46. src/llamafactory/data/__pycache__/data_utils.cpython-312.pyc +0 -0
  47. src/llamafactory/data/__pycache__/formatter.cpython-312.pyc +0 -0
  48. src/llamafactory/data/__pycache__/loader.cpython-312.pyc +0 -0
  49. src/llamafactory/data/__pycache__/mm_plugin.cpython-312.pyc +3 -0
  50. src/llamafactory/data/__pycache__/parser.cpython-312.pyc +0 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ src/llamafactory/data/__pycache__/mm_plugin.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
37
+ src/llamafactory/extras/__pycache__/constants.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,966 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ![# LLaMA Factory](assets/logo.png)
2
+
3
+ [![GitHub Repo stars](https://img.shields.io/github/stars/hiyouga/LLaMA-Factory?style=social)](https://github.com/hiyouga/LLaMA-Factory/stargazers)
4
+ [![GitHub last commit](https://img.shields.io/github/last-commit/hiyouga/LLaMA-Factory)](https://github.com/hiyouga/LLaMA-Factory/commits/main)
5
+ [![GitHub contributors](https://img.shields.io/github/contributors/hiyouga/LLaMA-Factory?color=orange)](https://github.com/hiyouga/LLaMA-Factory/graphs/contributors)
6
+ [![GitHub workflow](https://github.com/hiyouga/LLaMA-Factory/actions/workflows/tests.yml/badge.svg)](https://github.com/hiyouga/LLaMA-Factory/actions/workflows/tests.yml)
7
+ [![PyPI](https://img.shields.io/pypi/v/llamafactory)](https://pypi.org/project/llamafactory/)
8
+ [![Citation](https://img.shields.io/badge/citation-1000+-green)](https://scholar.google.com/scholar?cites=12620864006390196564)
9
+ [![Docker Pulls](https://img.shields.io/docker/pulls/hiyouga/llamafactory)](https://hub.docker.com/r/hiyouga/llamafactory/tags)
10
+
11
+ [![Twitter](https://img.shields.io/twitter/follow/llamafactory_ai)](https://twitter.com/llamafactory_ai)
12
+ [![Discord](assets/thirdparty/discord.svg)](https://discord.gg/rKfvV9r9FK)
13
+ [![WeChat](https://img.shields.io/badge/WeChat-User%20Group-blue?logo=wechat)](https://github.com/hiyouga/llamafactory-community)
14
+ [![Blog](https://img.shields.io/badge/Hugo-Official%20Blog-blue?logo=hugo)](https://blog.llamafactory.net/en/)
15
+
16
+ [![Open in Colab](assets/thirdparty/colab.svg)](https://colab.research.google.com/drive/1eRTPn37ltBbYsISy9Aw2NuI2Aq5CQrD9?usp=sharing)
17
+ [![Open in DSW](assets/thirdparty/dsw.svg)](https://gallery.pai-ml.com/#/preview/deepLearning/nlp/llama_factory)
18
+ [![Open in Spaces](https://img.shields.io/badge/🤗-Open%20in%20Spaces-blue)](https://huggingface.co/spaces/hiyouga/LLaMA-Board)
19
+ [![Open in Studios](https://img.shields.io/badge/ModelScope-Open%20in%20Studios-blue)](https://modelscope.cn/studios/hiyouga/LLaMA-Board)
20
+ [![Open in Novita](https://img.shields.io/badge/Novita-Deploy%20Template-blue)](https://novita.ai/templates-library/105981?sharer=88115474-394e-4bda-968e-b88e123d0c47)
21
+
22
+ ### Used by [Amazon](https://aws.amazon.com/cn/blogs/machine-learning/how-apoidea-group-enhances-visual-information-extraction-from-banking-documents-with-multimodal-models-using-llama-factory-on-amazon-sagemaker-hyperpod/), [NVIDIA](https://developer.nvidia.com/rtx/ai-toolkit), [Aliyun](https://help.aliyun.com/zh/pai/use-cases/fine-tune-a-llama-3-model-with-llama-factory), etc.
23
+
24
+ <div align="center" markdown="1">
25
+
26
+ ### Supporters ❤️
27
+
28
+ | <div style="text-align: center;"><a href="https://warp.dev/llama-factory"><img alt="Warp sponsorship" width="400" src="assets/sponsors/warp.jpg"></a><br><a href="https://warp.dev/llama-factory" style="font-size:larger;">Warp, the agentic terminal for developers</a><br><a href="https://warp.dev/llama-factory">Available for MacOS, Linux, & Windows</a> | <a href="https://serpapi.com"><img alt="SerpAPI sponsorship" width="250" src="assets/sponsors/serpapi.svg"> </a> |
29
+ | ---- | ---- |
30
+
31
+ ----
32
+
33
+ ### Easily fine-tune 100+ large language models with zero-code [CLI](#quickstart) and [Web UI](#fine-tuning-with-llama-board-gui-powered-by-gradio)
34
+
35
+ ![GitHub Trend](https://trendshift.io/api/badge/repositories/4535)
36
+
37
+ </div>
38
+
39
+ 👋 Join our [WeChat](https://github.com/hiyouga/llamafactory-community/blob/main/wechat/main.jpg) and [NPU](https://github.com/hiyouga/llamafactory-community/blob/main/wechat/npu.jpg) user groups.
40
+
41
+ \[ English | [中文](README_zh.md) \]
42
+
43
+ **Fine-tuning a large language model can be easy as...**
44
+
45
+ https://github.com/user-attachments/assets/3991a3a8-4276-4d30-9cab-4cb0c4b9b99e
46
+
47
+ Start local training:
48
+ - Please refer to [usage](#getting-started)
49
+
50
+ Start cloud training:
51
+ - **Colab (free)**: https://colab.research.google.com/drive/1eRTPn37ltBbYsISy9Aw2NuI2Aq5CQrD9?usp=sharing
52
+ - **PAI-DSW (free trial)**: https://gallery.pai-ml.com/#/preview/deepLearning/nlp/llama_factory
53
+
54
+ Read technical notes:
55
+ - **Documentation (WIP)**: https://llamafactory.readthedocs.io/en/latest/
56
+ - **Documentation (AMD GPU)**: https://rocm.docs.amd.com/projects/ai-developer-hub/en/latest/notebooks/fine_tune/llama_factory_llama3.html
57
+ - **Documentation (ASCEND NPU)**: https://llamafactory.readthedocs.io/en/latest/multibackend/npu/index.html
58
+ - **Official Blog**: https://blog.llamafactory.net/en/
59
+
60
+ > [!NOTE]
61
+ > Except for the above links, all other websites are unauthorized third-party websites. Please carefully use them.
62
+
63
+ ## Table of Contents
64
+
65
+ - [Features](#features)
66
+ - [Blogs](#blogs)
67
+ - [Changelog](#changelog)
68
+ - [Supported Models](#supported-models)
69
+ - [Supported Training Approaches](#supported-training-approaches)
70
+ - [Provided Datasets](#provided-datasets)
71
+ - [Requirement](#requirement)
72
+ - [Getting Started](#getting-started)
73
+ - [Installation](#installation)
74
+ - [Data Preparation](#data-preparation)
75
+ - [Quickstart](#quickstart)
76
+ - [Fine-Tuning with LLaMA Board GUI](#fine-tuning-with-llama-board-gui-powered-by-gradio)
77
+ - [Build Docker](#build-docker)
78
+ - [Deploy with OpenAI-style API and vLLM](#deploy-with-openai-style-api-and-vllm)
79
+ - [Download from ModelScope Hub](#download-from-modelscope-hub)
80
+ - [Download from Modelers Hub](#download-from-modelers-hub)
81
+ - [Use W&B Logger](#use-wb-logger)
82
+ - [Use SwanLab Logger](#use-swanlab-logger)
83
+ - [Projects using LLaMA Factory](#projects-using-llama-factory)
84
+ - [License](#license)
85
+ - [Citation](#citation)
86
+ - [Acknowledgement](#acknowledgement)
87
+
88
+ ## Features
89
+
90
+ - **Various models**: LLaMA, LLaVA, Mistral, Mixtral-MoE, Qwen3, Qwen3-VL, DeepSeek, Gemma, GLM, Phi, etc.
91
+ - **Integrated methods**: (Continuous) pre-training, (multimodal) supervised fine-tuning, reward modeling, PPO, DPO, KTO, ORPO, etc.
92
+ - **Scalable resources**: 16-bit full-tuning, freeze-tuning, LoRA and 2/3/4/5/6/8-bit QLoRA via AQLM/AWQ/GPTQ/LLM.int8/HQQ/EETQ.
93
+ - **Advanced algorithms**: [GaLore](https://github.com/jiaweizzhao/GaLore), [BAdam](https://github.com/Ledzy/BAdam), [APOLLO](https://github.com/zhuhanqing/APOLLO), [Adam-mini](https://github.com/zyushun/Adam-mini), [Muon](https://github.com/KellerJordan/Muon), [OFT](https://github.com/huggingface/peft/tree/main/src/peft/tuners/oft), DoRA, LongLoRA, LLaMA Pro, Mixture-of-Depths, LoRA+, LoftQ and PiSSA.
94
+ - **Practical tricks**: [FlashAttention-2](https://github.com/Dao-AILab/flash-attention), [Unsloth](https://github.com/unslothai/unsloth), [Liger Kernel](https://github.com/linkedin/Liger-Kernel), [KTransformers](https://github.com/kvcache-ai/ktransformers/), RoPE scaling, NEFTune and rsLoRA.
95
+ - **Wide tasks**: Multi-turn dialogue, tool using, image understanding, visual grounding, video recognition, audio understanding, etc.
96
+ - **Experiment monitors**: LlamaBoard, TensorBoard, Wandb, MLflow, [SwanLab](https://github.com/SwanHubX/SwanLab), etc.
97
+ - **Faster inference**: OpenAI-style API, Gradio UI and CLI with [vLLM worker](https://github.com/vllm-project/vllm) or [SGLang worker](https://github.com/sgl-project/sglang).
98
+
99
+ ### Day-N Support for Fine-Tuning Cutting-Edge Models
100
+
101
+ | Support Date | Model Name |
102
+ | ------------ | -------------------------------------------------------------------- |
103
+ | Day 0 | Qwen3 / Qwen2.5-VL / Gemma 3 / GLM-4.1V / InternLM 3 / MiniCPM-o-2.6 |
104
+ | Day 1 | Llama 3 / GLM-4 / Mistral Small / PaliGemma2 / Llama 4 |
105
+
106
+ ## Blogs
107
+
108
+ > [!TIP]
109
+ > Now we have a dedicated blog for LLaMA Factory!
110
+ >
111
+ > Website: https://blog.llamafactory.net/en/
112
+
113
+ - 💡 [KTransformers Fine-Tuning × LLaMA Factory: Fine-tuning 1000 Billion models with 2 4090-GPU + CPU](https://blog.llamafactory.net/en/posts/ktransformers/) (English)
114
+ - 💡 [Easy Dataset × LLaMA Factory: Enabling LLMs to Efficiently Learn Domain Knowledge](https://buaa-act.feishu.cn/wiki/GVzlwYcRFiR8OLkHbL6cQpYin7g) (English)
115
+ - 💡 [DataFlow × LLaMA Factory: Producing High-Quality Data for LLM Training with a Data Preparation Pipeline](https://wcny4qa9krto.feishu.cn/wiki/LWkkwTDBfiiRKqkDSvucG6yjnbW) (English) | [中文](https://wcny4qa9krto.feishu.cn/wiki/LlMxweUAJimrmykRD5qcGuswnHd)
116
+ - 💡 [DataFlex × LLaMA Factory: A Data-Centric Dynamic Training System Built on LLaMA-Factory](https://wcny4qa9krto.feishu.cn/wiki/OlREwPQWdi9K6ZkJNHIciLhtnkv) (English) | [中文](https://wcny4qa9krto.feishu.cn/wiki/H2A9wSsbCinzavkT2oyc2C5Vn0e)
117
+ - [A One-Stop Code-Free Model Reinforcement Learning and Deployment Platform based on LLaMA-Factory and EasyR1](https://aws.amazon.com/cn/blogs/china/building-llm-model-hub-based-on-llamafactory-and-easyr1/) (Chinese)
118
+ - [How Apoidea Group enhances visual information extraction from banking documents with multimodal models using LLaMA-Factory on Amazon SageMaker HyperPod](https://aws.amazon.com/cn/blogs/machine-learning/how-apoidea-group-enhances-visual-information-extraction-from-banking-documents-with-multimodal-models-using-llama-factory-on-amazon-sagemaker-hyperpod/) (English)
119
+
120
+ <details><summary>All Blogs</summary>
121
+
122
+ - [LLaMA Factory: Fine-tuning the DeepSeek-R1-Distill-Qwen-7B Model for News Classifier](https://gallery.pai-ml.com/#/preview/deepLearning/nlp/llama_factory_deepseek_r1_distill_7b) (Chinese)
123
+ - [A One-Stop Code-Free Model Fine-Tuning \& Deployment Platform based on SageMaker and LLaMA-Factory](https://aws.amazon.com/cn/blogs/china/a-one-stop-code-free-model-fine-tuning-deployment-platform-based-on-sagemaker-and-llama-factory/) (Chinese)
124
+ - [LLaMA Factory Multi-Modal Fine-Tuning Practice: Fine-Tuning Qwen2-VL for Personal Tourist Guide](https://gallery.pai-ml.com/#/preview/deepLearning/nlp/llama_factory_qwen2vl) (Chinese)
125
+ - [LLaMA Factory: Fine-tuning Llama3 for Role-Playing](https://gallery.pai-ml.com/#/preview/deepLearning/nlp/llama_factory) (Chinese)
126
+
127
+ </details>
128
+
129
+ ## Changelog
130
+
131
+ [25/10/26] We support Megatron-core training backend with [**mcore_adapter**](https://github.com/alibaba/ROLL/tree/main/mcore_adapter). See [PR #9237](https://github.com/hiyouga/LLaMA-Factory/pull/9237) to get started.
132
+
133
+ [25/08/22] We supported **[OFT](https://arxiv.org/abs/2306.07280)** and **[OFTv2](https://arxiv.org/abs/2506.19847)**. See [examples](examples/README.md) for usage.
134
+
135
+ [25/08/20] We supported fine-tuning the **[Intern-S1-mini](https://huggingface.co/internlm/Intern-S1-mini)** models. See [PR #8976](https://github.com/hiyouga/LLaMA-Factory/pull/8976) to get started.
136
+
137
+ [25/08/06] We supported fine-tuning the **[GPT-OSS](https://github.com/openai/gpt-oss)** models. See [PR #8826](https://github.com/hiyouga/LLaMA-Factory/pull/8826) to get started.
138
+
139
+ <details><summary>Full Changelog</summary>
140
+
141
+ [25/07/02] We supported fine-tuning the **[GLM-4.1V-9B-Thinking](https://github.com/THUDM/GLM-4.1V-Thinking)** model.
142
+
143
+ [25/04/28] We supported fine-tuning the **[Qwen3](https://qwenlm.github.io/blog/qwen3/)** model family.
144
+
145
+ [25/04/21] We supported the **[Muon](https://github.com/KellerJordan/Muon)** optimizer. See [examples](examples/README.md) for usage. Thank [@tianshijing](https://github.com/tianshijing)'s PR.
146
+
147
+ [25/04/16] We supported fine-tuning the **[InternVL3](https://huggingface.co/OpenGVLab/InternVL3-8B)** model. See [PR #7258](https://github.com/hiyouga/LLaMA-Factory/pull/7258) to get started.
148
+
149
+ [25/04/14] We supported fine-tuning the **[GLM-Z1](https://huggingface.co/THUDM/GLM-Z1-9B-0414)** and **[Kimi-VL](https://huggingface.co/moonshotai/Kimi-VL-A3B-Instruct)** models.
150
+
151
+ [25/04/06] We supported fine-tuning the **[Llama 4](https://ai.meta.com/blog/llama-4-multimodal-intelligence/)** model. See [PR #7611](https://github.com/hiyouga/LLaMA-Factory/pull/7611) to get started.
152
+
153
+ [25/03/31] We supported fine-tuning the **[Qwen2.5 Omni](https://qwenlm.github.io/blog/qwen2.5-omni/)** model. See [PR #7537](https://github.com/hiyouga/LLaMA-Factory/pull/7537) to get started.
154
+
155
+ [25/03/15] We supported **[SGLang](https://github.com/sgl-project/sglang)** as inference backend. Try `infer_backend: sglang` to accelerate inference.
156
+
157
+ [25/03/12] We supported fine-tuning the **[Gemma 3](https://huggingface.co/blog/gemma3)** model.
158
+
159
+ [25/02/24] Announcing **[EasyR1](https://github.com/hiyouga/EasyR1)**, an efficient, scalable and multi-modality RL training framework for efficient GRPO training.
160
+
161
+ [25/02/11] We supported saving the **[Ollama](https://github.com/ollama/ollama)** modelfile when exporting the model checkpoints. See [examples](examples/README.md) for usage.
162
+
163
+ [25/02/05] We supported fine-tuning the **[Qwen2-Audio](Qwen/Qwen2-Audio-7B-Instruct)** and **[MiniCPM-o-2.6](https://huggingface.co/openbmb/MiniCPM-o-2_6)** on audio understanding tasks.
164
+
165
+ [25/01/31] We supported fine-tuning the **[DeepSeek-R1](https://huggingface.co/deepseek-ai/DeepSeek-R1)** and **[Qwen2.5-VL](https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct)** models.
166
+
167
+ [25/01/15] We supported **[APOLLO](https://arxiv.org/abs/2412.05270)** optimizer. See [examples](examples/README.md) for usage.
168
+
169
+ [25/01/14] We supported fine-tuning the **[MiniCPM-o-2.6](https://huggingface.co/openbmb/MiniCPM-o-2_6)** and **[MiniCPM-V-2.6](https://huggingface.co/openbmb/MiniCPM-V-2_6)** models. Thank [@BUAADreamer](https://github.com/BUAADreamer)'s PR.
170
+
171
+ [25/01/14] We supported fine-tuning the **[InternLM 3](https://huggingface.co/collections/internlm/)** models. Thank [@hhaAndroid](https://github.com/hhaAndroid)'s PR.
172
+
173
+ [25/01/10] We supported fine-tuning the **[Phi-4](https://huggingface.co/microsoft/phi-4)** model.
174
+
175
+ [24/12/21] We supported using **[SwanLab](https://github.com/SwanHubX/SwanLab)** for experiment tracking and visualization. See [this section](#use-swanlab-logger) for details.
176
+
177
+ [24/11/27] We supported fine-tuning the **[Skywork-o1](https://huggingface.co/Skywork/Skywork-o1-Open-Llama-3.1-8B)** model and the **[OpenO1](https://huggingface.co/datasets/O1-OPEN/OpenO1-SFT)** dataset.
178
+
179
+ [24/10/09] We supported downloading pre-trained models and datasets from the **[Modelers Hub](https://modelers.cn/models)**. See [this tutorial](#download-from-modelers-hub) for usage.
180
+
181
+ [24/09/19] We supported fine-tuning the **[Qwen2.5](https://qwenlm.github.io/blog/qwen2.5/)** models.
182
+
183
+ [24/08/30] We supported fine-tuning the **[Qwen2-VL](https://qwenlm.github.io/blog/qwen2-vl/)** models. Thank [@simonJJJ](https://github.com/simonJJJ)'s PR.
184
+
185
+ [24/08/27] We supported **[Liger Kernel](https://github.com/linkedin/Liger-Kernel)**. Try `enable_liger_kernel: true` for efficient training.
186
+
187
+ [24/08/09] We supported **[Adam-mini](https://github.com/zyushun/Adam-mini)** optimizer. See [examples](examples/README.md) for usage. Thank [@relic-yuexi](https://github.com/relic-yuexi)'s PR.
188
+
189
+ [24/07/04] We supported [contamination-free packed training](https://github.com/MeetKai/functionary/tree/main/functionary/train/packing). Use `neat_packing: true` to activate it. Thank [@chuan298](https://github.com/chuan298)'s PR.
190
+
191
+ [24/06/16] We supported **[PiSSA](https://arxiv.org/abs/2404.02948)** algorithm. See [examples](examples/README.md) for usage.
192
+
193
+ [24/06/07] We supported fine-tuning the **[Qwen2](https://qwenlm.github.io/blog/qwen2/)** and **[GLM-4](https://github.com/THUDM/GLM-4)** models.
194
+
195
+ [24/05/26] We supported **[SimPO](https://arxiv.org/abs/2405.14734)** algorithm for preference learning. See [examples](examples/README.md) for usage.
196
+
197
+ [24/05/20] We supported fine-tuning the **PaliGemma** series models. Note that the PaliGemma models are pre-trained models, you need to fine-tune them with `paligemma` template for chat completion.
198
+
199
+ [24/05/18] We supported **[KTO](https://arxiv.org/abs/2402.01306)** algorithm for preference learning. See [examples](examples/README.md) for usage.
200
+
201
+ [24/05/14] We supported training and inference on the Ascend NPU devices. Check [installation](#installation) section for details.
202
+
203
+ [24/04/26] We supported fine-tuning the **LLaVA-1.5** multimodal LLMs. See [examples](examples/README.md) for usage.
204
+
205
+ [24/04/22] We provided a **[Colab notebook](https://colab.research.google.com/drive/1eRTPn37ltBbYsISy9Aw2NuI2Aq5CQrD9?usp=sharing)** for fine-tuning the Llama-3 model on a free T4 GPU. Two Llama-3-derived models fine-tuned using LLaMA Factory are available at Hugging Face, check [Llama3-8B-Chinese-Chat](https://huggingface.co/shenzhi-wang/Llama3-8B-Chinese-Chat) and [Llama3-Chinese](https://huggingface.co/zhichen/Llama3-Chinese) for details.
206
+
207
+ [24/04/21] We supported **[Mixture-of-Depths](https://arxiv.org/abs/2404.02258)** according to [AstraMindAI's implementation](https://github.com/astramind-ai/Mixture-of-depths). See [examples](examples/README.md) for usage.
208
+
209
+ [24/04/16] We supported **[BAdam](https://arxiv.org/abs/2404.02827)** optimizer. See [examples](examples/README.md) for usage.
210
+
211
+ [24/04/16] We supported **[unsloth](https://github.com/unslothai/unsloth)**'s long-sequence training (Llama-2-7B-56k within 24GB). It achieves **117%** speed and **50%** memory compared with FlashAttention-2, more benchmarks can be found in [this page](https://github.com/hiyouga/LLaMA-Factory/wiki/Performance-comparison).
212
+
213
+ [24/03/31] We supported **[ORPO](https://arxiv.org/abs/2403.07691)**. See [examples](examples/README.md) for usage.
214
+
215
+ [24/03/21] Our paper "[LlamaFactory: Unified Efficient Fine-Tuning of 100+ Language Models](https://arxiv.org/abs/2403.13372)" is available at arXiv!
216
+
217
+ [24/03/20] We supported **FSDP+QLoRA** that fine-tunes a 70B model on 2x24GB GPUs. See [examples](examples/README.md) for usage.
218
+
219
+ [24/03/13] We supported **[LoRA+](https://arxiv.org/abs/2402.12354)**. See [examples](examples/README.md) for usage.
220
+
221
+ [24/03/07] We supported **[GaLore](https://arxiv.org/abs/2403.03507)** optimizer. See [examples](examples/README.md) for usage.
222
+
223
+ [24/03/07] We integrated **[vLLM](https://github.com/vllm-project/vllm)** for faster and concurrent inference. Try `infer_backend: vllm` to enjoy **270%** inference speed.
224
+
225
+ [24/02/28] We supported weight-decomposed LoRA (**[DoRA](https://arxiv.org/abs/2402.09353)**). Try `use_dora: true` to activate DoRA training.
226
+
227
+ [24/02/15] We supported **block expansion** proposed by [LLaMA Pro](https://github.com/TencentARC/LLaMA-Pro). See [examples](examples/README.md) for usage.
228
+
229
+ [24/02/05] Qwen1.5 (Qwen2 beta version) series models are supported in LLaMA-Factory. Check this [blog post](https://qwenlm.github.io/blog/qwen1.5/) for details.
230
+
231
+ [24/01/18] We supported **agent tuning** for most models, equipping model with tool using abilities by fine-tuning with `dataset: glaive_toolcall_en`.
232
+
233
+ [23/12/23] We supported **[unsloth](https://github.com/unslothai/unsloth)**'s implementation to boost LoRA tuning for the LLaMA, Mistral and Yi models. Try `use_unsloth: true` argument to activate unsloth patch. It achieves **170%** speed in our benchmark, check [this page](https://github.com/hiyouga/LLaMA-Factory/wiki/Performance-comparison) for details.
234
+
235
+ [23/12/12] We supported fine-tuning the latest MoE model **[Mixtral 8x7B](https://huggingface.co/mistralai/Mixtral-8x7B-v0.1)** in our framework. See hardware requirement [here](#hardware-requirement).
236
+
237
+ [23/12/01] We supported downloading pre-trained models and datasets from the **[ModelScope Hub](https://modelscope.cn/models)**. See [this tutorial](#download-from-modelscope-hub) for usage.
238
+
239
+ [23/10/21] We supported **[NEFTune](https://arxiv.org/abs/2310.05914)** trick for fine-tuning. Try `neftune_noise_alpha: 5` argument to activate NEFTune.
240
+
241
+ [23/09/27] We supported **$S^2$-Attn** proposed by [LongLoRA](https://github.com/dvlab-research/LongLoRA) for the LLaMA models. Try `shift_attn: true` argument to enable shift short attention.
242
+
243
+ [23/09/23] We integrated MMLU, C-Eval and CMMLU benchmarks in this repo. See [examples](examples/README.md) for usage.
244
+
245
+ [23/09/10] We supported **[FlashAttention-2](https://github.com/Dao-AILab/flash-attention)**. Try `flash_attn: fa2` argument to enable FlashAttention-2 if you are using RTX4090, A100 or H100 GPUs.
246
+
247
+ [23/08/12] We supported **RoPE scaling** to extend the context length of the LLaMA models. Try `rope_scaling: linear` argument in training and `rope_scaling: dynamic` argument at inference to extrapolate the position embeddings.
248
+
249
+ [23/08/11] We supported **[DPO training](https://arxiv.org/abs/2305.18290)** for instruction-tuned models. See [examples](examples/README.md) for usage.
250
+
251
+ [23/07/31] We supported **dataset streaming**. Try `streaming: true` and `max_steps: 10000` arguments to load your dataset in streaming mode.
252
+
253
+ [23/07/29] We released two instruction-tuned 13B models at Hugging Face. See these Hugging Face Repos ([LLaMA-2](https://huggingface.co/hiyouga/Llama-2-Chinese-13b-chat) / [Baichuan](https://huggingface.co/hiyouga/Baichuan-13B-sft)) for details.
254
+
255
+ [23/07/18] We developed an **all-in-one Web UI** for training, evaluation and inference. Try `train_web.py` to fine-tune models in your Web browser. Thank [@KanadeSiina](https://github.com/KanadeSiina) and [@codemayq](https://github.com/codemayq) for their efforts in the development.
256
+
257
+ [23/07/09] We released **[FastEdit](https://github.com/hiyouga/FastEdit)** ⚡🩹, an easy-to-use package for editing the factual knowledge of large language models efficiently. Please follow [FastEdit](https://github.com/hiyouga/FastEdit) if you are interested.
258
+
259
+ [23/06/29] We provided a **reproducible example** of training a chat model using instruction-following datasets, see [Baichuan-7B-sft](https://huggingface.co/hiyouga/Baichuan-7B-sft) for details.
260
+
261
+ [23/06/22] We aligned the [demo API](src/api_demo.py) with the [OpenAI's](https://platform.openai.com/docs/api-reference/chat) format where you can insert the fine-tuned model in **arbitrary ChatGPT-based applications**.
262
+
263
+ [23/06/03] We supported quantized training and inference (aka **[QLoRA](https://github.com/artidoro/qlora)**). See [examples](examples/README.md) for usage.
264
+
265
+ </details>
266
+
267
+ > [!TIP]
268
+ > If you cannot use the latest feature, please pull the latest code and install LLaMA-Factory again.
269
+
270
+ ## Supported Models
271
+
272
+ | Model | Model size | Template |
273
+ | ----------------------------------------------------------------- | -------------------------------- | -------------------- |
274
+ | [BLOOM/BLOOMZ](https://huggingface.co/bigscience) | 560M/1.1B/1.7B/3B/7.1B/176B | - |
275
+ | [DeepSeek (LLM/Code/MoE)](https://huggingface.co/deepseek-ai) | 7B/16B/67B/236B | deepseek |
276
+ | [DeepSeek 3-3.2](https://huggingface.co/deepseek-ai) | 236B/671B | deepseek3 |
277
+ | [DeepSeek R1 (Distill)](https://huggingface.co/deepseek-ai) | 1.5B/7B/8B/14B/32B/70B/671B | deepseekr1 |
278
+ | [ERNIE-4.5](https://huggingface.co/baidu) | 0.3B/21B/300B | ernie_nothink |
279
+ | [Falcon/Falcon H1](https://huggingface.co/tiiuae) | 0.5B/1.5B/3B/7B/11B/34B/40B/180B | falcon/falcon_h1 |
280
+ | [Gemma/Gemma 2/CodeGemma](https://huggingface.co/google) | 2B/7B/9B/27B | gemma/gemma2 |
281
+ | [Gemma 3/Gemma 3n](https://huggingface.co/google) | 270M/1B/4B/6B/8B/12B/27B | gemma3/gemma3n |
282
+ | [GLM-4/GLM-4-0414/GLM-Z1](https://huggingface.co/zai-org) | 9B/32B | glm4/glmz1 |
283
+ | [GLM-4.5/GLM-4.5(6)V](https://huggingface.co/zai-org) | 9B/106B/355B | glm4_moe/glm4_5v |
284
+ | [GPT-2](https://huggingface.co/openai-community) | 0.1B/0.4B/0.8B/1.5B | - |
285
+ | [GPT-OSS](https://huggingface.co/openai) | 20B/120B | gpt_oss |
286
+ | [Granite 3-4](https://huggingface.co/ibm-granite) | 1B/2B/3B/7B/8B | granite3/granite4 |
287
+ | [Hunyuan/Hunyuan1.5 (MT)](https://huggingface.co/tencent/) | 0.5B/1.8B/4B/7B/13B | hunyuan/hunyuan_small|
288
+ | [InternLM 2-3](https://huggingface.co/internlm) | 7B/8B/20B | intern2 |
289
+ | [InternVL 2.5-3.5](https://huggingface.co/OpenGVLab) | 1B/2B/4B/8B/14B/30B/38B/78B/241B | intern_vl |
290
+ | [Intern-S1-mini](https://huggingface.co/internlm/) | 8B | intern_s1 |
291
+ | [Kimi-VL](https://huggingface.co/moonshotai) | 16B | kimi_vl |
292
+ | [Ling 2.0 (mini/flash)](https://huggingface.co/inclusionAI) | 16B/100B | bailing_v2 |
293
+ | [LFM 2.5 (VL)](https://huggingface.co/LiquidAI) | 1.2B/1.6B | lfm2/lfm2_vl |
294
+ | [Llama](https://github.com/facebookresearch/llama) | 7B/13B/33B/65B | - |
295
+ | [Llama 2](https://huggingface.co/meta-llama) | 7B/13B/70B | llama2 |
296
+ | [Llama 3-3.3](https://huggingface.co/meta-llama) | 1B/3B/8B/70B | llama3 |
297
+ | [Llama 4](https://huggingface.co/meta-llama) | 109B/402B | llama4 |
298
+ | [Llama 3.2 Vision](https://huggingface.co/meta-llama) | 11B/90B | mllama |
299
+ | [LLaVA-1.5](https://huggingface.co/llava-hf) | 7B/13B | llava |
300
+ | [LLaVA-NeXT](https://huggingface.co/llava-hf) | 7B/8B/13B/34B/72B/110B | llava_next |
301
+ | [LLaVA-NeXT-Video](https://huggingface.co/llava-hf) | 7B/34B | llava_next_video |
302
+ | [MiMo](https://huggingface.co/XiaomiMiMo) | 7B/309B | mimo/mimo_v2 |
303
+ | [MiniCPM 4/5](https://huggingface.co/openbmb) | 0.5B/1B/8B | cpm4/empty |
304
+ | [MiniCPM-o/MiniCPM-V 4.5](https://huggingface.co/openbmb) | 8B/9B | minicpm_o/minicpm_v |
305
+ | [MiniCPM-V 4.6](https://huggingface.co/openbmb) | 3B/8B | minicpm_v_4_6 |
306
+ | [MiniMax-M1/MiniMax-M2](https://huggingface.co/MiniMaxAI/models) | 229B/456B | minimax1/minimax2 |
307
+ | [Ministral 3](https://huggingface.co/mistralai) | 3B/8B/14B | ministral3 |
308
+ | [Mistral/Mixtral](https://huggingface.co/mistralai) | 7B/8x7B/8x22B | mistral |
309
+ | [PaliGemma/PaliGemma2](https://huggingface.co/google) | 3B/10B/28B | paligemma |
310
+ | [Phi-3/Phi-3.5](https://huggingface.co/microsoft) | 4B/14B | phi |
311
+ | [Phi-3-small](https://huggingface.co/microsoft) | 7B | phi_small |
312
+ | [Phi-4-mini/Phi-4](https://huggingface.co/microsoft) | 3.8B/14B | phi4_mini/phi4 |
313
+ | [Pixtral](https://huggingface.co/mistralai) | 12B | pixtral |
314
+ | [Qwen2 (Code/Math/MoE/QwQ)](https://huggingface.co/Qwen) | 0.5B/1.5B/3B/7B/14B/32B/72B/110B | qwen |
315
+ | [Qwen3 (MoE/Instruct/Thinking/Next)](https://huggingface.co/Qwen) | 0.6B/1.7B/4B/8B/14B/32B/80B/235B | qwen3/qwen3_nothink |
316
+ | [Qwen3.5](https://huggingface.co/Qwen) | 0.8B/2B/4B/9B/27B/35B/122B/397B | qwen3_5/qwen3_5_nothink |
317
+ | [Qwen3.6](https://huggingface.co/Qwen) | 27B/35B | qwen3_6 |
318
+ | [Qwen2-Audio](https://huggingface.co/Qwen) | 7B | qwen2_audio |
319
+ | [Qwen2.5-Omni](https://huggingface.co/Qwen) | 3B/7B | qwen2_omni |
320
+ | [Qwen3-Omni](https://huggingface.co/Qwen) | 30B | qwen3_omni |
321
+ | [Qwen2-VL/Qwen2.5-VL/QVQ](https://huggingface.co/Qwen) | 2B/3B/7B/32B/72B | qwen2_vl |
322
+ | [Qwen3-VL](https://huggingface.co/Qwen) | 2B/4B/8B/30B/32B/235B | qwen3_vl |
323
+ | [Seed (OSS/Coder)](https://huggingface.co/ByteDance-Seed) | 8B/36B | seed_oss/seed_coder |
324
+ | [StarCoder 2](https://huggingface.co/bigcode) | 3B/7B/15B | - |
325
+ | [TeleChat 2-2.5](https://huggingface.co/Tele-AI) | 3B/7B/35B/115B | telechat2 |
326
+ | [Yuan 2](https://huggingface.co/IEITYuan) | 2B/51B/102B | yuan |
327
+
328
+ > [!NOTE]
329
+ > For the "base" models, the `template` argument can be chosen from `default`, `alpaca`, `vicuna` etc. But make sure to use the **corresponding template** for the "instruct/chat" models.
330
+ >
331
+ > If the model has both reasoning and non-reasoning versions, please use the `_nothink` suffix to distinguish between them. For example, `qwen3` and `qwen3_nothink`.
332
+ >
333
+ > Remember to use the **SAME** template in training and inference.
334
+ >
335
+ > \*: You should install the `transformers` from main branch and use `DISABLE_VERSION_CHECK=1` to skip version check.
336
+ >
337
+ > \*\*: You need to install a specific version of `transformers` to use the corresponding model.
338
+
339
+ Please refer to [constants.py](src/llamafactory/extras/constants.py) for a full list of models we supported.
340
+
341
+ You also can add a custom chat template to [template.py](src/llamafactory/data/template.py).
342
+
343
+ ## Supported Training Approaches
344
+
345
+ | Approach | Full-tuning | Freeze-tuning | LoRA | QLoRA | OFT | QOFT |
346
+ | ---------------------- | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | ------------------ |
347
+ | Pre-Training | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
348
+ | Supervised Fine-Tuning | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
349
+ | Reward Modeling | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
350
+ | PPO Training | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
351
+ | DPO Training | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
352
+ | KTO Training | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
353
+ | ORPO Training | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
354
+ | SimPO Training | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
355
+
356
+ > [!TIP]
357
+ > The implementation details of PPO can be found in [this blog](https://newfacade.github.io/notes-on-reinforcement-learning/17-ppo-trl.html).
358
+
359
+ ## Provided Datasets
360
+
361
+ <details><summary>Pre-training datasets</summary>
362
+
363
+ - [Wiki Demo (en)](data/wiki_demo.txt)
364
+ - [RefinedWeb (en)](https://huggingface.co/datasets/tiiuae/falcon-refinedweb)
365
+ - [RedPajama V2 (en)](https://huggingface.co/datasets/togethercomputer/RedPajama-Data-V2)
366
+ - [Wikipedia (en)](https://huggingface.co/datasets/olm/olm-wikipedia-20221220)
367
+ - [Wikipedia (zh)](https://huggingface.co/datasets/pleisto/wikipedia-cn-20230720-filtered)
368
+ - [Pile (en)](https://huggingface.co/datasets/EleutherAI/pile)
369
+ - [SkyPile (zh)](https://huggingface.co/datasets/Skywork/SkyPile-150B)
370
+ - [FineWeb (en)](https://huggingface.co/datasets/HuggingFaceFW/fineweb)
371
+ - [FineWeb-Edu (en)](https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu)
372
+ - [CCI3-HQ (zh)](https://huggingface.co/datasets/BAAI/CCI3-HQ)
373
+ - [CCI3-Data (zh)](https://huggingface.co/datasets/BAAI/CCI3-Data)
374
+ - [CCI4.0-M2-Base-v1 (en&zh)](https://huggingface.co/datasets/BAAI/CCI4.0-M2-Base-v1)
375
+ - [CCI4.0-M2-CoT-v1 (en&zh)](https://huggingface.co/datasets/BAAI/CCI4.0-M2-CoT-v1)
376
+ - [CCI4.0-M2-Extra-v1 (en&zh)](https://huggingface.co/datasets/BAAI/CCI4.0-M2-Extra-v1)
377
+ - [The Stack (en)](https://huggingface.co/datasets/bigcode/the-stack)
378
+ - [StarCoder (en)](https://huggingface.co/datasets/bigcode/starcoderdata)
379
+
380
+ </details>
381
+
382
+ <details><summary>Supervised fine-tuning datasets</summary>
383
+
384
+ - [Identity (en&zh)](data/identity.json)
385
+ - [Stanford Alpaca (en)](https://github.com/tatsu-lab/stanford_alpaca)
386
+ - [Stanford Alpaca (zh)](https://github.com/ymcui/Chinese-LLaMA-Alpaca-3)
387
+ - [Alpaca GPT4 (en&zh)](https://github.com/Instruction-Tuning-with-GPT-4/GPT-4-LLM)
388
+ - [Glaive Function Calling V2 (en&zh)](https://huggingface.co/datasets/glaiveai/glaive-function-calling-v2)
389
+ - [LIMA (en)](https://huggingface.co/datasets/GAIR/lima)
390
+ - [Guanaco Dataset (multilingual)](https://huggingface.co/datasets/JosephusCheung/GuanacoDataset)
391
+ - [BELLE 2M (zh)](https://huggingface.co/datasets/BelleGroup/train_2M_CN)
392
+ - [BELLE 1M (zh)](https://huggingface.co/datasets/BelleGroup/train_1M_CN)
393
+ - [BELLE 0.5M (zh)](https://huggingface.co/datasets/BelleGroup/train_0.5M_CN)
394
+ - [BELLE Dialogue 0.4M (zh)](https://huggingface.co/datasets/BelleGroup/generated_chat_0.4M)
395
+ - [BELLE School Math 0.25M (zh)](https://huggingface.co/datasets/BelleGroup/school_math_0.25M)
396
+ - [BELLE Multiturn Chat 0.8M (zh)](https://huggingface.co/datasets/BelleGroup/multiturn_chat_0.8M)
397
+ - [UltraChat (en)](https://github.com/thunlp/UltraChat)
398
+ - [OpenPlatypus (en)](https://huggingface.co/datasets/garage-bAInd/Open-Platypus)
399
+ - [CodeAlpaca 20k (en)](https://huggingface.co/datasets/sahil2801/CodeAlpaca-20k)
400
+ - [Alpaca CoT (multilingual)](https://huggingface.co/datasets/QingyiSi/Alpaca-CoT)
401
+ - [OpenOrca (en)](https://huggingface.co/datasets/Open-Orca/OpenOrca)
402
+ - [SlimOrca (en)](https://huggingface.co/datasets/Open-Orca/SlimOrca)
403
+ - [MathInstruct (en)](https://huggingface.co/datasets/TIGER-Lab/MathInstruct)
404
+ - [Firefly 1.1M (zh)](https://huggingface.co/datasets/YeungNLP/firefly-train-1.1M)
405
+ - [Wiki QA (en)](https://huggingface.co/datasets/wiki_qa)
406
+ - [Web QA (zh)](https://huggingface.co/datasets/suolyer/webqa)
407
+ - [WebNovel (zh)](https://huggingface.co/datasets/zxbsmk/webnovel_cn)
408
+ - [Nectar (en)](https://huggingface.co/datasets/berkeley-nest/Nectar)
409
+ - [deepctrl (en&zh)](https://www.modelscope.cn/datasets/deepctrl/deepctrl-sft-data)
410
+ - [Advertise Generating (zh)](https://huggingface.co/datasets/HasturOfficial/adgen)
411
+ - [ShareGPT Hyperfiltered (en)](https://huggingface.co/datasets/totally-not-an-llm/sharegpt-hyperfiltered-3k)
412
+ - [ShareGPT4 (en&zh)](https://huggingface.co/datasets/shibing624/sharegpt_gpt4)
413
+ - [UltraChat 200k (en)](https://huggingface.co/datasets/HuggingFaceH4/ultrachat_200k)
414
+ - [Infinity Instruct (zh)](https://huggingface.co/datasets/BAAI/Infinity-Instruct)
415
+ - [AgentInstruct (en)](https://huggingface.co/datasets/THUDM/AgentInstruct)
416
+ - [LMSYS Chat 1M (en)](https://huggingface.co/datasets/lmsys/lmsys-chat-1m)
417
+ - [Evol Instruct V2 (en)](https://huggingface.co/datasets/WizardLM/WizardLM_evol_instruct_V2_196k)
418
+ - [Cosmopedia (en)](https://huggingface.co/datasets/HuggingFaceTB/cosmopedia)
419
+ - [STEM (zh)](https://huggingface.co/datasets/hfl/stem_zh_instruction)
420
+ - [Ruozhiba (zh)](https://huggingface.co/datasets/hfl/ruozhiba_gpt4_turbo)
421
+ - [Neo-sft (zh)](https://huggingface.co/datasets/m-a-p/neo_sft_phase2)
422
+ - [Magpie-Pro-300K-Filtered (en)](https://huggingface.co/datasets/Magpie-Align/Magpie-Pro-300K-Filtered)
423
+ - [Magpie-ultra-v0.1 (en)](https://huggingface.co/datasets/argilla/magpie-ultra-v0.1)
424
+ - [WebInstructSub (en)](https://huggingface.co/datasets/TIGER-Lab/WebInstructSub)
425
+ - [OpenO1-SFT (en&zh)](https://huggingface.co/datasets/O1-OPEN/OpenO1-SFT)
426
+ - [Open-Thoughts (en)](https://huggingface.co/datasets/open-thoughts/OpenThoughts-114k)
427
+ - [Open-R1-Math (en)](https://huggingface.co/datasets/open-r1/OpenR1-Math-220k)
428
+ - [Chinese-DeepSeek-R1-Distill (zh)](https://huggingface.co/datasets/Congliu/Chinese-DeepSeek-R1-Distill-data-110k-SFT)
429
+ - [LLaVA mixed (en&zh)](https://huggingface.co/datasets/BUAADreamer/llava-en-zh-300k)
430
+ - [Pokemon-gpt4o-captions (en&zh)](https://huggingface.co/datasets/jugg1024/pokemon-gpt4o-captions)
431
+ - [DLR-Web (en)](https://huggingface.co/datasets/Attention1115/DLR-Web)
432
+ - [Open Assistant (de)](https://huggingface.co/datasets/mayflowergmbh/oasst_de)
433
+ - [Dolly 15k (de)](https://huggingface.co/datasets/mayflowergmbh/dolly-15k_de)
434
+ - [Alpaca GPT4 (de)](https://huggingface.co/datasets/mayflowergmbh/alpaca-gpt4_de)
435
+ - [OpenSchnabeltier (de)](https://huggingface.co/datasets/mayflowergmbh/openschnabeltier_de)
436
+ - [Evol Instruct (de)](https://huggingface.co/datasets/mayflowergmbh/evol-instruct_de)
437
+ - [Dolphin (de)](https://huggingface.co/datasets/mayflowergmbh/dolphin_de)
438
+ - [Booksum (de)](https://huggingface.co/datasets/mayflowergmbh/booksum_de)
439
+ - [Airoboros (de)](https://huggingface.co/datasets/mayflowergmbh/airoboros-3.0_de)
440
+ - [Ultrachat (de)](https://huggingface.co/datasets/mayflowergmbh/ultra-chat_de)
441
+
442
+ </details>
443
+
444
+ <details><summary>Preference datasets</summary>
445
+
446
+ - [DPO mixed (en&zh)](https://huggingface.co/datasets/hiyouga/DPO-En-Zh-20k)
447
+ - [UltraFeedback (en)](https://huggingface.co/datasets/HuggingFaceH4/ultrafeedback_binarized)
448
+ - [COIG-P (zh)](https://huggingface.co/datasets/m-a-p/COIG-P)
449
+ - [RLHF-V (en)](https://huggingface.co/datasets/openbmb/RLHF-V-Dataset)
450
+ - [VLFeedback (en)](https://huggingface.co/datasets/Zhihui/VLFeedback)
451
+ - [RLAIF-V (en)](https://huggingface.co/datasets/openbmb/RLAIF-V-Dataset)
452
+ - [Orca DPO Pairs (en)](https://huggingface.co/datasets/Intel/orca_dpo_pairs)
453
+ - [HH-RLHF (en)](https://huggingface.co/datasets/Anthropic/hh-rlhf)
454
+ - [Nectar (en)](https://huggingface.co/datasets/berkeley-nest/Nectar)
455
+ - [Orca DPO (de)](https://huggingface.co/datasets/mayflowergmbh/intel_orca_dpo_pairs_de)
456
+ - [KTO mixed (en)](https://huggingface.co/datasets/argilla/kto-mix-15k)
457
+
458
+ </details>
459
+
460
+ Some datasets require confirmation before using them, so we recommend logging in with your Hugging Face account using these commands.
461
+
462
+ ```bash
463
+ pip install "huggingface_hub<1.0.0"
464
+ huggingface-cli login
465
+ ```
466
+
467
+ ## Requirement
468
+
469
+ | Mandatory | Minimum | Recommend |
470
+ | ------------ | ------- | --------- |
471
+ | python | 3.11 | >=3.11 |
472
+ | torch | 2.0.0 | 2.6.0 |
473
+ | torchvision | 0.15.0 | 0.21.0 |
474
+ | transformers | 4.49.0 | 4.50.0 |
475
+ | datasets | 2.16.0 | 3.2.0 |
476
+ | accelerate | 0.34.0 | 1.2.1 |
477
+ | peft | 0.14.0 | 0.15.1 |
478
+ | trl | 0.8.6 | 0.9.6 |
479
+
480
+ | Optional | Minimum | Recommend |
481
+ | ------------ | ------- | --------- |
482
+ | CUDA | 11.6 | 12.2 |
483
+ | deepspeed | 0.10.0 | 0.16.4 |
484
+ | bitsandbytes | 0.39.0 | 0.43.1 |
485
+ | vllm | 0.4.3 | 0.8.2 |
486
+ | flash-attn | 2.5.6 | 2.7.2 |
487
+
488
+ ### Hardware Requirement
489
+
490
+ \* *estimated*
491
+
492
+ | Method | Bits | 7B | 14B | 30B | 70B | `x`B |
493
+ | ----------------------------------- | ---- | ----- | ----- | ----- | ------ | ------- |
494
+ | Full (`bf16` or `fp16`) | 32 | 120GB | 240GB | 600GB | 1200GB | `18x`GB |
495
+ | Full (`pure_bf16`) | 16 | 60GB | 120GB | 300GB | 600GB | `8x`GB |
496
+ | Freeze/LoRA/GaLore/APOLLO/BAdam/OFT | 16 | 16GB | 32GB | 64GB | 160GB | `2x`GB |
497
+ | QLoRA / QOFT | 8 | 10GB | 20GB | 40GB | 80GB | `x`GB |
498
+ | QLoRA / QOFT | 4 | 6GB | 12GB | 24GB | 48GB | `x/2`GB |
499
+ | QLoRA / QOFT | 2 | 4GB | 8GB | 16GB | 24GB | `x/4`GB |
500
+
501
+ ## Getting Started
502
+
503
+ ### Installation
504
+
505
+ > [!IMPORTANT]
506
+ > Installation is mandatory.
507
+
508
+ #### Install from Source
509
+
510
+ ```bash
511
+ git clone --depth 1 https://github.com/hiyouga/LlamaFactory.git
512
+ cd LlamaFactory
513
+ pip install -e .
514
+ pip install -r requirements/metrics.txt
515
+ ```
516
+
517
+ Optional dependencies available: `metrics`, `deepspeed`. Install with: `pip install -e . && pip install -r requirements/metrics.txt -r requirements/deepspeed.txt`
518
+
519
+ Additional dependencies for specific features are available in `examples/requirements/`.
520
+
521
+ #### Install from Docker Image
522
+
523
+ ```bash
524
+ docker run -it --rm --gpus=all --ipc=host hiyouga/llamafactory:latest
525
+ ```
526
+
527
+ This image is built on Ubuntu 22.04 (x86\_64), CUDA 12.4, Python 3.11, PyTorch 2.6.0, and Flash-attn 2.7.4.
528
+
529
+ Find the pre-built images: https://hub.docker.com/r/hiyouga/llamafactory/tags
530
+
531
+ Please refer to [build docker](#build-docker) to build the image yourself.
532
+
533
+ <details><summary>Setting up a virtual environment with <b>uv</b></summary>
534
+
535
+ Create an isolated Python environment with [uv](https://github.com/astral-sh/uv):
536
+
537
+ ```bash
538
+ uv run llamafactory-cli webui
539
+ ```
540
+
541
+ </details>
542
+
543
+ <details><summary>For Windows users</summary>
544
+
545
+ #### Install PyTorch
546
+
547
+ You need to manually install the GPU version of PyTorch on the Windows platform. Please refer to the [official website](https://pytorch.org/get-started/locally/) and the following command to install PyTorch with CUDA support:
548
+
549
+ ```bash
550
+ pip uninstall torch torchvision torchaudio
551
+ pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
552
+ python -c "import torch; print(torch.cuda.is_available())"
553
+ ```
554
+
555
+ If you see `True` then you have successfully installed PyTorch with CUDA support.
556
+
557
+ Try `dataloader_num_workers: 0` if you encounter `Can't pickle local object` error.
558
+
559
+ #### Install BitsAndBytes
560
+
561
+ To enable Quantized LoRA (QLoRA) on Windows, you need to install bitsandbytes.
562
+
563
+ For most users, it is recommended to install the latest official release:
564
+
565
+ ```bash
566
+ pip install bitsandbytes
567
+ ```
568
+
569
+ If you are using uv to manage your virtual environment, it is recommended to install bitsandbytes after installing the GPU-enabled version of PyTorch:
570
+
571
+ ```bash
572
+ uv pip install bitsandbytes --no-deps
573
+ ```
574
+
575
+ [!IMPORTANT]
576
+ Pay attention to the CUDA Toolkit version when installing bitsandbytes. Official bitsandbytes releases are built for specific CUDA Toolkit versions. On Windows x86-64, separate builds are currently provided for CUDA 11.8–12.6 and CUDA 12.8–12.9. Support for NVIDIA RTX 50 Series GPUs (e.g., RTX 5060 Ti, sm_120) requires the CUDA 12.8–12.9 builds.
577
+
578
+ If your environment uses an older CUDA version, or you need compatibility with older Windows / PyTorch combinations, you can install the third-party precompiled Windows wheel:
579
+
580
+ ```bash
581
+ pip install https://github.com/jllllll/bitsandbytes-windows-webui/releases/download/wheels/bitsandbytes-0.41.2.post2-py3-none-win_amd64.whl
582
+ ```
583
+
584
+ #### Install Flash Attention-2
585
+
586
+ To enable FlashAttention-2 on the Windows platform, please use the script from [flash-attention-windows-wheel](https://huggingface.co/lldacing/flash-attention-windows-wheel) to compile and install it by yourself.
587
+
588
+ </details>
589
+
590
+ <details><summary>For Ascend NPU users</summary>
591
+
592
+ To install LLaMA Factory on Ascend NPU devices, please upgrade Python to version 3.10 or higher: `pip install -r requirements/npu.txt`. Additionally, you need to install the **Ascend CANN Toolkit and Kernels**. Please follow the [installation tutorial](https://llamafactory.readthedocs.io/en/latest/advanced/npu_installation.html).
593
+
594
+
595
+ You can also download the pre-built Docker images:
596
+
597
+ ```bash
598
+ # Docker Hub
599
+ docker pull hiyouga/llamafactory:latest-npu-a2
600
+ docker pull hiyouga/llamafactory:latest-npu-a3
601
+
602
+ # quay.io
603
+ docker pull quay.io/ascend/llamafactory:latest-npu-a2
604
+ docker pull quay.io/ascend/llamafactory:latest-npu-a3
605
+ ```
606
+
607
+ #### Install BitsAndBytes
608
+
609
+ To use QLoRA based on bitsandbytes on Ascend NPU, please follow these 3 steps:
610
+
611
+ 1. Manually compile bitsandbytes: Refer to [the installation documentation](https://huggingface.co/docs/bitsandbytes/installation?backend=Ascend+NPU&platform=Ascend+NPU) for the NPU version of bitsandbytes to complete the compilation and installation. The compilation requires a cmake version of at least 3.22.1 and a g++ version of at least 12.x.
612
+
613
+ ```bash
614
+ # Install bitsandbytes from source
615
+ # Clone bitsandbytes repo, Ascend NPU backend is currently enabled on multi-backend-refactor branch
616
+ git clone -b multi-backend-refactor https://github.com/bitsandbytes-foundation/bitsandbytes.git
617
+ cd bitsandbytes/
618
+
619
+ # Install dependencies
620
+ pip install -r requirements-dev.txt
621
+
622
+ # Install the dependencies for the compilation tools. Note that the commands for this step may vary depending on the operating system. The following are provided for reference
623
+ apt-get install -y build-essential cmake
624
+
625
+ # Compile & install
626
+ cmake -DCOMPUTE_BACKEND=npu -S .
627
+ make
628
+ pip install .
629
+ ```
630
+
631
+ 2. Install transformers from the main branch.
632
+
633
+ ```bash
634
+ git clone -b main https://github.com/huggingface/transformers.git
635
+ cd transformers
636
+ pip install .
637
+ ```
638
+
639
+ 3. Set `double_quantization: false` in the configuration. You can refer to the [example](examples/train_qlora/qwen3_lora_sft_bnb_npu.yaml).
640
+
641
+ </details>
642
+
643
+ ### Data Preparation
644
+
645
+ Please refer to [data/README.md](data/README.md) for checking the details about the format of dataset files. You can use datasets on HuggingFace / ModelScope / Modelers hub, load the dataset in local disk, or specify a path to s3/gcs cloud storage.
646
+
647
+ > [!NOTE]
648
+ > Please update `data/dataset_info.json` to use your custom dataset.
649
+
650
+ You can also use **[Easy Dataset](https://github.com/ConardLi/easy-dataset)**, **[DataFlow](https://github.com/OpenDCAI/DataFlow)** and **[GraphGen](https://github.com/open-sciencelab/GraphGen)** to create synthetic data for fine-tuning.
651
+
652
+ ### Quickstart
653
+
654
+ Use the following 3 commands to run LoRA **fine-tuning**, **inference** and **merging** of the Qwen3-4B-Instruct model, respectively.
655
+
656
+ ```bash
657
+ llamafactory-cli train examples/train_lora/qwen3_lora_sft.yaml
658
+ llamafactory-cli chat examples/inference/qwen3_lora_sft.yaml
659
+ llamafactory-cli export examples/merge_lora/qwen3_lora_sft.yaml
660
+ ```
661
+
662
+ See [examples/README.md](examples/README.md) for advanced usage (including distributed training).
663
+
664
+ > [!TIP]
665
+ > Use `llamafactory-cli help` to show help information.
666
+ >
667
+ > Read [FAQs](https://github.com/hiyouga/LLaMA-Factory/issues/4614) first if you encounter any problems.
668
+
669
+ ### Fine-Tuning with LLaMA Board GUI (powered by [Gradio](https://github.com/gradio-app/gradio))
670
+
671
+ ```bash
672
+ llamafactory-cli webui
673
+ ```
674
+
675
+ ### Build Docker
676
+
677
+ For CUDA users:
678
+
679
+ ```bash
680
+ cd docker/docker-cuda/
681
+ docker compose up -d
682
+ docker compose exec llamafactory bash
683
+ ```
684
+
685
+ For Ascend NPU users:
686
+
687
+ ```bash
688
+ cd docker/docker-npu/
689
+ docker compose up -d
690
+ docker compose exec llamafactory bash
691
+ ```
692
+
693
+ For AMD ROCm users:
694
+
695
+ ```bash
696
+ cd docker/docker-rocm/
697
+ docker compose up -d
698
+ docker compose exec llamafactory bash
699
+ ```
700
+
701
+ <details><summary>Build without Docker Compose</summary>
702
+
703
+ For CUDA users:
704
+
705
+ ```bash
706
+ docker build -f ./docker/docker-cuda/Dockerfile \
707
+ --build-arg PIP_INDEX=https://pypi.org/simple \
708
+ -t llamafactory:latest .
709
+
710
+ docker run -dit --ipc=host --gpus=all \
711
+ -p 7860:7860 \
712
+ -p 8000:8000 \
713
+ --name llamafactory \
714
+ llamafactory:latest
715
+
716
+ docker exec -it llamafactory bash
717
+ ```
718
+
719
+ For Ascend NPU users:
720
+
721
+ ```bash
722
+ docker build -f ./docker/docker-npu/Dockerfile \
723
+ --build-arg PIP_INDEX=https://pypi.org/simple \
724
+ -t llamafactory:latest .
725
+
726
+ docker run -dit --ipc=host \
727
+ -v /usr/local/dcmi:/usr/local/dcmi \
728
+ -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
729
+ -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
730
+ -v /etc/ascend_install.info:/etc/ascend_install.info \
731
+ -p 7860:7860 \
732
+ -p 8000:8000 \
733
+ --device /dev/davinci0 \
734
+ --device /dev/davinci_manager \
735
+ --device /dev/devmm_svm \
736
+ --device /dev/hisi_hdc \
737
+ --name llamafactory \
738
+ llamafactory:latest
739
+
740
+ docker exec -it llamafactory bash
741
+ ```
742
+
743
+ For AMD ROCm users:
744
+
745
+ ```bash
746
+ docker build -f ./docker/docker-rocm/Dockerfile \
747
+ --build-arg PIP_INDEX=https://pypi.org/simple \
748
+ -t llamafactory:latest .
749
+
750
+ docker run -dit --ipc=host \
751
+ -p 7860:7860 \
752
+ -p 8000:8000 \
753
+ --device /dev/kfd \
754
+ --device /dev/dri \
755
+ --name llamafactory \
756
+ llamafactory:latest
757
+
758
+ docker exec -it llamafactory bash
759
+ ```
760
+
761
+ </details>
762
+
763
+ <details><summary>Use Docker volumes</summary>
764
+
765
+ You can uncomment `VOLUME [ "/root/.cache/huggingface", "/app/shared_data", "/app/output" ]` in the Dockerfile to use data volumes.
766
+
767
+ When building the Docker image, use `-v ./hf_cache:/root/.cache/huggingface` argument to mount the local directory to the container. The following data volumes are available.
768
+
769
+ - `hf_cache`: Utilize Hugging Face cache on the host machine.
770
+ - `shared_data`: The directionary to store datasets on the host machine.
771
+ - `output`: Set export dir to this location so that the merged result can be accessed directly on the host machine.
772
+
773
+ </details>
774
+
775
+ ### Deploy with OpenAI-style API and vLLM
776
+
777
+ ```bash
778
+ API_PORT=8000 llamafactory-cli api examples/inference/qwen3.yaml infer_backend=vllm vllm_enforce_eager=true
779
+ ```
780
+
781
+ > [!TIP]
782
+ > Visit [this page](https://platform.openai.com/docs/api-reference/chat/create) for API document.
783
+ >
784
+ > Examples: [Image understanding](scripts/api_example/test_image.py) | [Function calling](scripts/api_example/test_toolcall.py)
785
+
786
+ ### Download from ModelScope Hub
787
+
788
+ If you have trouble with downloading models and datasets from Hugging Face, you can use ModelScope.
789
+
790
+ ```bash
791
+ export USE_MODELSCOPE_HUB=1 # `set USE_MODELSCOPE_HUB=1` for Windows
792
+ ```
793
+
794
+ Train the model by specifying a model ID of the ModelScope Hub as the `model_name_or_path`. You can find a full list of model IDs at [ModelScope Hub](https://modelscope.cn/models), e.g., `LLM-Research/Meta-Llama-3-8B-Instruct`.
795
+
796
+ ### Download from Modelers Hub
797
+
798
+ You can also use Modelers Hub to download models and datasets.
799
+
800
+ ```bash
801
+ export USE_OPENMIND_HUB=1 # `set USE_OPENMIND_HUB=1` for Windows
802
+ ```
803
+
804
+ Train the model by specifying a model ID of the Modelers Hub as the `model_name_or_path`. You can find a full list of model IDs at [Modelers Hub](https://modelers.cn/models), e.g., `TeleAI/TeleChat-7B-pt`.
805
+
806
+ ### Use W&B Logger
807
+
808
+ To use [Weights & Biases](https://wandb.ai) for logging experimental results, you need to add the following arguments to yaml files.
809
+
810
+ ```yaml
811
+ report_to: wandb
812
+ run_name: test_run # optional
813
+ ```
814
+
815
+ Set `WANDB_API_KEY` to [your key](https://wandb.ai/authorize) when launching training tasks to log in with your W&B account.
816
+
817
+ ### Use SwanLab Logger
818
+
819
+ To use [SwanLab](https://github.com/SwanHubX/SwanLab) for logging experimental results, you need to add the following arguments to yaml files.
820
+
821
+ ```yaml
822
+ use_swanlab: true
823
+ swanlab_run_name: test_run # optional
824
+ ```
825
+
826
+ When launching training tasks, you can log in to SwanLab in three ways:
827
+
828
+ 1. Add `swanlab_api_key=<your_api_key>` to the yaml file, and set it to your [API key](https://swanlab.cn/settings).
829
+ 2. Set the environment variable `SWANLAB_API_KEY` to your [API key](https://swanlab.cn/settings).
830
+ 3. Use the `swanlab login` command to complete the login.
831
+
832
+ ## Projects using LLaMA Factory
833
+
834
+ If you have a project that should be incorporated, please contact via email or create a pull request.
835
+
836
+ <details><summary>Click to show</summary>
837
+
838
+ 1. Wang et al. ESRL: Efficient Sampling-based Reinforcement Learning for Sequence Generation. 2023. [[arxiv]](https://arxiv.org/abs/2308.02223)
839
+ 1. Yu et al. Open, Closed, or Small Language Models for Text Classification? 2023. [[arxiv]](https://arxiv.org/abs/2308.10092)
840
+ 1. Wang et al. UbiPhysio: Support Daily Functioning, Fitness, and Rehabilitation with Action Understanding and Feedback in Natural Language. 2023. [[arxiv]](https://arxiv.org/abs/2308.10526)
841
+ 1. Luceri et al. Leveraging Large Language Models to Detect Influence Campaigns in Social Media. 2023. [[arxiv]](https://arxiv.org/abs/2311.07816)
842
+ 1. Zhang et al. Alleviating Hallucinations of Large Language Models through Induced Hallucinations. 2023. [[arxiv]](https://arxiv.org/abs/2312.15710)
843
+ 1. Wang et al. Know Your Needs Better: Towards Structured Understanding of Marketer Demands with Analogical Reasoning Augmented LLMs. KDD 2024. [[arxiv]](https://arxiv.org/abs/2401.04319)
844
+ 1. Wang et al. CANDLE: Iterative Conceptualization and Instantiation Distillation from Large Language Models for Commonsense Reasoning. ACL 2024. [[arxiv]](https://arxiv.org/abs/2401.07286)
845
+ 1. Choi et al. FACT-GPT: Fact-Checking Augmentation via Claim Matching with LLMs. 2024. [[arxiv]](https://arxiv.org/abs/2402.05904)
846
+ 1. Zhang et al. AutoMathText: Autonomous Data Selection with Language Models for Mathematical Texts. 2024. [[arxiv]](https://arxiv.org/abs/2402.07625)
847
+ 1. Lyu et al. KnowTuning: Knowledge-aware Fine-tuning for Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2402.11176)
848
+ 1. Yang et al. LaCo: Large Language Model Pruning via Layer Collapse. 2024. [[arxiv]](https://arxiv.org/abs/2402.11187)
849
+ 1. Bhardwaj et al. Language Models are Homer Simpson! Safety Re-Alignment of Fine-tuned Language Models through Task Arithmetic. 2024. [[arxiv]](https://arxiv.org/abs/2402.11746)
850
+ 1. Yang et al. Enhancing Empathetic Response Generation by Augmenting LLMs with Small-scale Empathetic Models. 2024. [[arxiv]](https://arxiv.org/abs/2402.11801)
851
+ 1. Yi et al. Generation Meets Verification: Accelerating Large Language Model Inference with Smart Parallel Auto-Correct Decoding. ACL 2024 Findings. [[arxiv]](https://arxiv.org/abs/2402.11809)
852
+ 1. Cao et al. Head-wise Shareable Attention for Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2402.11819)
853
+ 1. Zhang et al. Enhancing Multilingual Capabilities of Large Language Models through Self-Distillation from Resource-Rich Languages. 2024. [[arxiv]](https://arxiv.org/abs/2402.12204)
854
+ 1. Kim et al. Efficient and Effective Vocabulary Expansion Towards Multilingual Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2402.14714)
855
+ 1. Yu et al. KIEval: A Knowledge-grounded Interactive Evaluation Framework for Large Language Models. ACL 2024. [[arxiv]](https://arxiv.org/abs/2402.15043)
856
+ 1. Huang et al. Key-Point-Driven Data Synthesis with its Enhancement on Mathematical Reasoning. 2024. [[arxiv]](https://arxiv.org/abs/2403.02333)
857
+ 1. Duan et al. Negating Negatives: Alignment without Human Positive Samples via Distributional Dispreference Optimization. 2024. [[arxiv]](https://arxiv.org/abs/2403.03419)
858
+ 1. Xie and Schwertfeger. Empowering Robotics with Large Language Models: osmAG Map Comprehension with LLMs. 2024. [[arxiv]](https://arxiv.org/abs/2403.08228)
859
+ 1. Wu et al. Large Language Models are Parallel Multilingual Learners. 2024. [[arxiv]](https://arxiv.org/abs/2403.09073)
860
+ 1. Zhang et al. EDT: Improving Large Language Models' Generation by Entropy-based Dynamic Temperature Sampling. 2024. [[arxiv]](https://arxiv.org/abs/2403.14541)
861
+ 1. Weller et al. FollowIR: Evaluating and Teaching Information Retrieval Models to Follow Instructions. 2024. [[arxiv]](https://arxiv.org/abs/2403.15246)
862
+ 1. Hongbin Na. CBT-LLM: A Chinese Large Language Model for Cognitive Behavioral Therapy-based Mental Health Question Answering. COLING 2024. [[arxiv]](https://arxiv.org/abs/2403.16008)
863
+ 1. Zan et al. CodeS: Natural Language to Code Repository via Multi-Layer Sketch. 2024. [[arxiv]](https://arxiv.org/abs/2403.16443)
864
+ 1. Liu et al. Extensive Self-Contrast Enables Feedback-Free Language Model Alignment. 2024. [[arxiv]](https://arxiv.org/abs/2404.00604)
865
+ 1. Luo et al. BAdam: A Memory Efficient Full Parameter Training Method for Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.02827)
866
+ 1. Du et al. Chinese Tiny LLM: Pretraining a Chinese-Centric Large Language Model. 2024. [[arxiv]](https://arxiv.org/abs/2404.04167)
867
+ 1. Ma et al. Parameter Efficient Quasi-Orthogonal Fine-Tuning via Givens Rotation. ICML 2024. [[arxiv]](https://arxiv.org/abs/2404.04316)
868
+ 1. Liu et al. Dynamic Generation of Personalities with Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.07084)
869
+ 1. Shang et al. How Far Have We Gone in Stripped Binary Code Understanding Using Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.09836)
870
+ 1. Huang et al. LLMTune: Accelerate Database Knob Tuning with Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.11581)
871
+ 1. Deng et al. Text-Tuple-Table: Towards Information Integration in Text-to-Table Generation via Global Tuple Extraction. 2024. [[arxiv]](https://arxiv.org/abs/2404.14215)
872
+ 1. Acikgoz et al. Hippocrates: An Open-Source Framework for Advancing Large Language Models in Healthcare. 2024. [[arxiv]](https://arxiv.org/abs/2404.16621)
873
+ 1. Zhang et al. Small Language Models Need Strong Verifiers to Self-Correct Reasoning. ACL 2024 Findings. [[arxiv]](https://arxiv.org/abs/2404.17140)
874
+ 1. Zhou et al. FREB-TQA: A Fine-Grained Robustness Evaluation Benchmark for Table Question Answering. NAACL 2024. [[arxiv]](https://arxiv.org/abs/2404.18585)
875
+ 1. Xu et al. Large Language Models for Cyber Security: A Systematic Literature Review. 2024. [[arxiv]](https://arxiv.org/abs/2405.04760)
876
+ 1. Dammu et al. "They are uncultured": Unveiling Covert Harms and Social Threats in LLM Generated Conversations. 2024. [[arxiv]](https://arxiv.org/abs/2405.05378)
877
+ 1. Yi et al. A safety realignment framework via subspace-oriented model fusion for large language models. 2024. [[arxiv]](https://arxiv.org/abs/2405.09055)
878
+ 1. Lou et al. SPO: Multi-Dimensional Preference Sequential Alignment With Implicit Reward Modeling. 2024. [[arxiv]](https://arxiv.org/abs/2405.12739)
879
+ 1. Zhang et al. Getting More from Less: Large Language Models are Good Spontaneous Multilingual Learners. 2024. [[arxiv]](https://arxiv.org/abs/2405.13816)
880
+ 1. Zhang et al. TS-Align: A Teacher-Student Collaborative Framework for Scalable Iterative Finetuning of Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2405.20215)
881
+ 1. Zihong Chen. Sentence Segmentation and Sentence Punctuation Based on XunziALLM. 2024. [[paper]](https://aclanthology.org/2024.lt4hala-1.30)
882
+ 1. Gao et al. The Best of Both Worlds: Toward an Honest and Helpful Large Language Model. 2024. [[arxiv]](https://arxiv.org/abs/2406.00380)
883
+ 1. Wang and Song. MARS: Benchmarking the Metaphysical Reasoning Abilities of Language Models with a Multi-task Evaluation Dataset. 2024. [[arxiv]](https://arxiv.org/abs/2406.02106)
884
+ 1. Hu et al. Computational Limits of Low-Rank Adaptation (LoRA) for Transformer-Based Models. 2024. [[arxiv]](https://arxiv.org/abs/2406.03136)
885
+ 1. Ge et al. Time Sensitive Knowledge Editing through Efficient Finetuning. ACL 2024. [[arxiv]](https://arxiv.org/abs/2406.04496)
886
+ 1. Tan et al. Peer Review as A Multi-Turn and Long-Context Dialogue with Role-Based Interactions. 2024. [[arxiv]](https://arxiv.org/abs/2406.05688)
887
+ 1. Song et al. Turbo Sparse: Achieving LLM SOTA Performance with Minimal Activated Parameters. 2024. [[arxiv]](https://arxiv.org/abs/2406.05955)
888
+ 1. Gu et al. RWKV-CLIP: A Robust Vision-Language Representation Learner. 2024. [[arxiv]](https://arxiv.org/abs/2406.06973)
889
+ 1. Chen et al. Advancing Tool-Augmented Large Language Models: Integrating Insights from Errors in Inference Trees. 2024. [[arxiv]](https://arxiv.org/abs/2406.07115)
890
+ 1. Zhu et al. Are Large Language Models Good Statisticians?. 2024. [[arxiv]](https://arxiv.org/abs/2406.07815)
891
+ 1. Li et al. Know the Unknown: An Uncertainty-Sensitive Method for LLM Instruction Tuning. 2024. [[arxiv]](https://arxiv.org/abs/2406.10099)
892
+ 1. Ding et al. IntentionQA: A Benchmark for Evaluating Purchase Intention Comprehension Abilities of Language Models in E-commerce. 2024. [[arxiv]](https://arxiv.org/abs/2406.10173)
893
+ 1. He et al. COMMUNITY-CROSS-INSTRUCT: Unsupervised Instruction Generation for Aligning Large Language Models to Online Communities. 2024. [[arxiv]](https://arxiv.org/abs/2406.12074)
894
+ 1. Lin et al. FVEL: Interactive Formal Verification Environment with Large Language Models via Theorem Proving. 2024. [[arxiv]](https://arxiv.org/abs/2406.14408)
895
+ 1. Treutlein et al. Connecting the Dots: LLMs can Infer and Verbalize Latent Structure from Disparate Training Data. 2024. [[arxiv]](https://arxiv.org/abs/2406.14546)
896
+ 1. Feng et al. SS-Bench: A Benchmark for Social Story Generation and Evaluation. 2024. [[arxiv]](https://arxiv.org/abs/2406.15695)
897
+ 1. Feng et al. Self-Constructed Context Decompilation with Fined-grained Alignment Enhancement. 2024. [[arxiv]](https://arxiv.org/abs/2406.17233)
898
+ 1. Liu et al. Large Language Models for Cuffless Blood Pressure Measurement From Wearable Biosignals. 2024. [[arxiv]](https://arxiv.org/abs/2406.18069)
899
+ 1. Iyer et al. Exploring Very Low-Resource Translation with LLMs: The University of Edinburgh's Submission to AmericasNLP 2024 Translation Task. AmericasNLP 2024. [[paper]](https://aclanthology.org/2024.americasnlp-1.25)
900
+ 1. Li et al. Calibrating LLMs with Preference Optimization on Thought Trees for Generating Rationale in Science Question Scoring. 2024. [[arxiv]](https://arxiv.org/abs/2406.19949)
901
+ 1. Yang et al. Financial Knowledge Large Language Model. 2024. [[arxiv]](https://arxiv.org/abs/2407.00365)
902
+ 1. Lin et al. DogeRM: Equipping Reward Models with Domain Knowledge through Model Merging. 2024. [[arxiv]](https://arxiv.org/abs/2407.01470)
903
+ 1. Bako et al. Evaluating the Semantic Profiling Abilities of LLMs for Natural Language Utterances in Data Visualization. 2024. [[arxiv]](https://arxiv.org/abs/2407.06129)
904
+ 1. Huang et al. RoLoRA: Fine-tuning Rotated Outlier-free LLMs for Effective Weight-Activation Quantization. 2024. [[arxiv]](https://arxiv.org/abs/2407.08044)
905
+ 1. Jiang et al. LLM-Collaboration on Automatic Science Journalism for the General Audience. 2024. [[arxiv]](https://arxiv.org/abs/2407.09756)
906
+ 1. Inouye et al. Applied Auto-tuning on LoRA Hyperparameters. 2024. [[paper]](https://scholarcommons.scu.edu/cseng_senior/272/)
907
+ 1. Qi et al. Research on Tibetan Tourism Viewpoints information generation system based on LLM. 2024. [[arxiv]](https://arxiv.org/abs/2407.13561)
908
+ 1. Xu et al. Course-Correction: Safety Alignment Using Synthetic Preferences. 2024. [[arxiv]](https://arxiv.org/abs/2407.16637)
909
+ 1. Sun et al. LAMBDA: A Large Model Based Data Agent. 2024. [[arxiv]](https://arxiv.org/abs/2407.17535)
910
+ 1. Zhu et al. CollectiveSFT: Scaling Large Language Models for Chinese Medical Benchmark with Collective Instructions in Healthcare. 2024. [[arxiv]](https://arxiv.org/abs/2407.19705)
911
+ 1. Yu et al. Correcting Negative Bias in Large Language Models through Negative Attention Score Alignment. 2024. [[arxiv]](https://arxiv.org/abs/2408.00137)
912
+ 1. Xie et al. The Power of Personalized Datasets: Advancing Chinese Composition Writing for Elementary School through Targeted Model Fine-Tuning. IALP 2024. [[paper]](https://www.asianlp.sg/conferences/ialp2024/proceedings/papers/IALP2024_P055.pdf)
913
+ 1. Liu et al. Instruct-Code-Llama: Improving Capabilities of Language Model in Competition Level Code Generation by Online Judge Feedback. ICIC 2024. [[paper]](https://link.springer.com/chapter/10.1007/978-981-97-5669-8_11)
914
+ 1. Wang et al. Cybernetic Sentinels: Unveiling the Impact of Safety Data Selection on Model Security in Supervised Fine-Tuning. ICIC 2024. [[paper]](https://link.springer.com/chapter/10.1007/978-981-97-5669-8_23)
915
+ 1. Xia et al. Understanding the Performance and Estimating the Cost of LLM Fine-Tuning. 2024. [[arxiv]](https://arxiv.org/abs/2408.04693)
916
+ 1. Zeng et al. Perceive, Reflect, and Plan: Designing LLM Agent for Goal-Directed City Navigation without Instructions. 2024. [[arxiv]](https://arxiv.org/abs/2408.04168)
917
+ 1. Xia et al. Using Pre-trained Language Model for Accurate ESG Prediction. FinNLP 2024. [[paper]](https://aclanthology.org/2024.finnlp-2.1/)
918
+ 1. Liang et al. I-SHEEP: Self-Alignment of LLM from Scratch through an Iterative Self-Enhancement Paradigm. 2024. [[arxiv]](https://arxiv.org/abs/2408.08072)
919
+ 1. Bai et al. Aligning Large Language Model with Direct Multi-Preference Optimization for Recommendation. CIKM 2024. [[paper]](https://dl.acm.org/doi/10.1145/3627673.3679611)
920
+ 1. Zhang et al. CPsyCoun: A Report-based Multi-turn Dialogue Reconstruction and Evaluation Framework for Chinese Psychological Counseling. ACL 2024. [[paper]](https://aclanthology.org/2024.findings-acl.830.pdf)
921
+ 1. **[StarWhisper](https://github.com/Yu-Yang-Li/StarWhisper)**: A large language model for Astronomy, based on ChatGLM2-6B and Qwen-14B.
922
+ 1. **[DISC-LawLLM](https://github.com/FudanDISC/DISC-LawLLM)**: A large language model specialized in Chinese legal domain, based on Baichuan-13B, is capable of retrieving and reasoning on legal knowledge.
923
+ 1. **[Sunsimiao](https://github.com/X-D-Lab/Sunsimiao)**: A large language model specialized in Chinese medical domain, based on Baichuan-7B and ChatGLM-6B.
924
+ 1. **[CareGPT](https://github.com/WangRongsheng/CareGPT)**: A series of large language models for Chinese medical domain, based on LLaMA2-7B and Baichuan-13B.
925
+ 1. **[MachineMindset](https://github.com/PKU-YuanGroup/Machine-Mindset/)**: A series of MBTI Personality large language models, capable of giving any LLM 16 different personality types based on different datasets and training methods.
926
+ 1. **[Luminia-13B-v3](https://huggingface.co/Nekochu/Luminia-13B-v3)**: A large language model specialized in generate metadata for stable diffusion. [[demo]](https://huggingface.co/spaces/Nekochu/Luminia-13B_SD_Prompt)
927
+ 1. **[Chinese-LLaVA-Med](https://github.com/BUAADreamer/Chinese-LLaVA-Med)**: A multimodal large language model specialized in Chinese medical domain, based on LLaVA-1.5-7B.
928
+ 1. **[AutoRE](https://github.com/THUDM/AutoRE)**: A document-level relation extraction system based on large language models.
929
+ 1. **[NVIDIA RTX AI Toolkit](https://github.com/NVIDIA/RTX-AI-Toolkit)**: SDKs for fine-tuning LLMs on Windows PC for NVIDIA RTX.
930
+ 1. **[LazyLLM](https://github.com/LazyAGI/LazyLLM)**: An easy and lazy way for building multi-agent LLMs applications and supports model fine-tuning via LLaMA Factory.
931
+ 1. **[RAG-Retrieval](https://github.com/NLPJCL/RAG-Retrieval)**: A full pipeline for RAG retrieval model fine-tuning, inference, and distillation. [[blog]](https://zhuanlan.zhihu.com/p/987727357)
932
+ 1. **[360-LLaMA-Factory](https://github.com/Qihoo360/360-LLaMA-Factory)**: A modified library that supports long sequence SFT & DPO using ring attention.
933
+ 1. **[Sky-T1](https://novasky-ai.github.io/posts/sky-t1/)**: An o1-like model fine-tuned by NovaSky AI with very small cost.
934
+ 1. **[WeClone](https://github.com/xming521/WeClone)**: One-stop solution for creating your digital avatar from chat logs.
935
+ 1. **[EmoLLM](https://github.com/SmartFlowAI/EmoLLM)**: A project about large language models (LLMs) and mental health.
936
+ </details>
937
+
938
+ ## License
939
+
940
+ This repository is licensed under the [Apache-2.0 License](LICENSE).
941
+
942
+ Please follow the model licenses to use the corresponding model weights: [BLOOM](https://huggingface.co/spaces/bigscience/license) / [DeepSeek](https://github.com/deepseek-ai/DeepSeek-LLM/blob/main/LICENSE-MODEL) / [Falcon](https://huggingface.co/tiiuae/falcon-180B/blob/main/LICENSE.txt) / [Gemma](https://ai.google.dev/gemma/terms) / [GLM-4](https://huggingface.co/THUDM/glm-4-9b/blob/main/LICENSE) / [GPT-2](https://github.com/openai/gpt-2/blob/master/LICENSE) / [Granite](LICENSE) / [InternLM](https://github.com/InternLM/InternLM#license) / [Llama](https://github.com/facebookresearch/llama/blob/main/MODEL_CARD.md) / [Llama 2](https://ai.meta.com/llama/license/) / [Llama 3](https://llama.meta.com/llama3/license/) / [Llama 4](https://github.com/meta-llama/llama-models/blob/main/models/llama4/LICENSE) / [MiniCPM](https://github.com/OpenBMB/MiniCPM/blob/main/MiniCPM%20Model%20License.md) / [Mistral/Mixtral/Pixtral](LICENSE) / [Phi-3/Phi-4](https://huggingface.co/microsoft/Phi-3-mini-4k-instruct/blob/main/LICENSE) / [Qwen](https://github.com/QwenLM/Qwen/blob/main/Tongyi%20Qianwen%20LICENSE%20AGREEMENT) / [StarCoder 2](https://huggingface.co/spaces/bigcode/bigcode-model-license-agreement) / [TeleChat2](https://huggingface.co/Tele-AI/telechat-7B/blob/main/TeleChat%E6%A8%A1%E5%9E%8B%E7%A4%BE%E5%8C%BA%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE.pdf) / [Yuan 2](https://github.com/IEIT-Yuan/Yuan-2.0/blob/main/LICENSE-Yuan)
943
+
944
+ ## Citation
945
+
946
+ If this work is helpful, please kindly cite as:
947
+
948
+ ```bibtex
949
+ @inproceedings{zheng2024llamafactory,
950
+ title={LlamaFactory: Unified Efficient Fine-Tuning of 100+ Language Models},
951
+ author={Yaowei Zheng and Richong Zhang and Junhao Zhang and Yanhan Ye and Zheyan Luo and Zhangchi Feng and Yongqiang Ma},
952
+ booktitle={Proceedings of the 62nd Annual Meeting of the Association for Computational Linguistics (Volume 3: System Demonstrations)},
953
+ address={Bangkok, Thailand},
954
+ publisher={Association for Computational Linguistics},
955
+ year={2024},
956
+ url={http://arxiv.org/abs/2403.13372}
957
+ }
958
+ ```
959
+
960
+ ## Acknowledgement
961
+
962
+ This repo benefits from [PEFT](https://github.com/huggingface/peft), [TRL](https://github.com/huggingface/trl), [QLoRA](https://github.com/artidoro/qlora) and [FastChat](https://github.com/lm-sys/FastChat). Thanks for their wonderful works.
963
+
964
+ ## Star History
965
+
966
+ ![Star History Chart](https://api.star-history.com/svg?repos=hiyouga/LLaMA-Factory&type=Date)
requirements.txt ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ accelerate==1.11.0
2
+ aiofiles==24.1.0
3
+ aiohappyeyeballs==2.7.1
4
+ aiohttp==3.14.1
5
+ aiosignal==1.4.0
6
+ annotated-doc==0.0.4
7
+ annotated-types==0.7.0
8
+ antlr4-python3-runtime==4.9.3
9
+ anyio==4.14.1
10
+ attrs==26.1.0
11
+ av==16.0.0
12
+ bitsandbytes==0.49.2
13
+ brotli==1.2.0
14
+ certifi==2026.6.17
15
+ charset-normalizer==3.4.9
16
+ click==8.4.2
17
+ contourpy==1.3.3
18
+ cuda-bindings==13.3.1
19
+ cuda-pathfinder==1.5.6
20
+ cuda-toolkit==13.0.3.0
21
+ cycler==0.12.1
22
+ datasets==4.0.0
23
+ dill==0.3.8
24
+ docstring_parser==0.18.0
25
+ einops==0.8.2
26
+ fastapi==0.139.0
27
+ ffmpy==1.0.0
28
+ filelock==3.29.7
29
+ fire==0.7.1
30
+ fonttools==4.63.0
31
+ frozenlist==1.8.0
32
+ fsspec==2025.3.0
33
+ gradio==5.50.0
34
+ gradio_client==1.14.0
35
+ groovy==0.1.2
36
+ h11==0.16.0
37
+ hf-xet==1.5.1
38
+ hf_transfer==0.1.9
39
+ httpcore==1.0.9
40
+ httpx==0.28.1
41
+ huggingface_hub==1.23.0
42
+ idna==3.18
43
+ Jinja2==3.1.6
44
+ kiwisolver==1.5.0
45
+ -e git+https://github.com/hiyouga/LLaMA-Factory.git@ea31c43d806162a7fd98065abfef2d974fff5766#egg=llamafactory
46
+ markdown-it-py==4.2.0
47
+ MarkupSafe==3.0.3
48
+ matplotlib==3.11.0
49
+ mdurl==0.1.2
50
+ modelscope==1.38.1
51
+ modelscope-hub==0.1.7
52
+ mpmath==1.3.0
53
+ multidict==6.7.1
54
+ multiprocess==0.70.16
55
+ networkx==3.6.1
56
+ numpy==2.5.1
57
+ nvidia-cublas==13.1.1.3
58
+ nvidia-cublas-cu12==12.4.5.8
59
+ nvidia-cuda-cupti==13.0.85
60
+ nvidia-cuda-cupti-cu12==12.4.127
61
+ nvidia-cuda-nvrtc==13.0.88
62
+ nvidia-cuda-nvrtc-cu12==12.4.127
63
+ nvidia-cuda-runtime==13.0.96
64
+ nvidia-cuda-runtime-cu12==12.4.127
65
+ nvidia-cudnn-cu12==9.1.0.70
66
+ nvidia-cudnn-cu13==9.20.0.48
67
+ nvidia-cufft==12.0.0.61
68
+ nvidia-cufft-cu12==11.2.1.3
69
+ nvidia-cufile==1.15.1.6
70
+ nvidia-curand==10.4.0.35
71
+ nvidia-curand-cu12==10.3.5.147
72
+ nvidia-cusolver==12.0.4.66
73
+ nvidia-cusolver-cu12==11.6.1.9
74
+ nvidia-cusparse==12.6.3.3
75
+ nvidia-cusparse-cu12==12.3.1.170
76
+ nvidia-cusparselt-cu12==0.6.2
77
+ nvidia-cusparselt-cu13==0.8.1
78
+ nvidia-nccl-cu12==2.21.5
79
+ nvidia-nccl-cu13==2.29.7
80
+ nvidia-nvjitlink==13.3.33
81
+ nvidia-nvjitlink-cu12==12.4.127
82
+ nvidia-nvshmem-cu13==3.4.5
83
+ nvidia-nvtx==13.0.85
84
+ nvidia-nvtx-cu12==12.4.127
85
+ omegaconf==2.3.1
86
+ orjson==3.11.9
87
+ packaging==26.2
88
+ pandas==2.3.3
89
+ peft==0.18.1
90
+ pillow==11.3.0
91
+ propcache==0.5.2
92
+ protobuf==7.35.1
93
+ psutil==7.2.2
94
+ pyarrow==25.0.0
95
+ pydantic==2.12.3
96
+ pydantic_core==2.41.4
97
+ pydub==0.25.1
98
+ Pygments==2.20.0
99
+ pyparsing==3.3.2
100
+ python-dateutil==2.9.0.post0
101
+ python-multipart==0.0.32
102
+ pytz==2026.2
103
+ PyYAML==6.0.3
104
+ regex==2026.6.28
105
+ requests==2.34.2
106
+ rich==15.0.0
107
+ ruff==0.15.21
108
+ safehttpx==0.1.7
109
+ safetensors==0.8.0
110
+ scipy==1.18.0
111
+ semantic-version==2.10.0
112
+ sentencepiece==0.2.1
113
+ setuptools==83.0.0
114
+ shellingham==1.5.4
115
+ shtab==1.8.1
116
+ six==1.17.0
117
+ sse-starlette==3.4.5
118
+ starlette==0.52.1
119
+ sympy==1.13.1
120
+ termcolor==3.3.0
121
+ tiktoken==0.13.0
122
+ tokenizers==0.22.2
123
+ tomlkit==0.13.3
124
+ torch==2.6.0+cu124
125
+ torchaudio==2.6.0+cu124
126
+ torchdata==0.11.0
127
+ torchvision==0.21.0+cu124
128
+ tqdm==4.68.4
129
+ transformers==5.7.0
130
+ triton==3.2.0
131
+ trl==0.24.0
132
+ typer==0.26.8
133
+ typing-inspection==0.4.2
134
+ typing_extensions==4.16.0
135
+ tyro==0.8.14
136
+ tzdata==2026.3
137
+ urllib3==2.7.0
138
+ uvicorn==0.51.0
139
+ websockets==15.0.1
140
+ xxhash==3.8.1
141
+ yarl==1.24.2
scripts/api_example/test_image.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import os
16
+
17
+ from openai import OpenAI
18
+ from transformers.utils.versions import require_version
19
+
20
+
21
+ require_version("openai>=1.5.0", "To fix: pip install openai>=1.5.0")
22
+
23
+
24
+ def main():
25
+ client = OpenAI(
26
+ api_key="{}".format(os.getenv("API_KEY", "0")),
27
+ base_url="http://localhost:{}/v1".format(os.getenv("API_PORT", 8000)),
28
+ )
29
+ messages = []
30
+ messages.append(
31
+ {
32
+ "role": "user",
33
+ "content": [
34
+ {"type": "text", "text": "Output the color and number of each box."},
35
+ {
36
+ "type": "image_url",
37
+ "image_url": {"url": "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2-VL/boxes.png"},
38
+ },
39
+ ],
40
+ }
41
+ )
42
+ result = client.chat.completions.create(messages=messages, model="test")
43
+ messages.append(result.choices[0].message)
44
+ print("Round 1:", result.choices[0].message.content)
45
+ # The image shows a pyramid of colored blocks with numbers on them. Here are the colors and numbers of ...
46
+ messages.append(
47
+ {
48
+ "role": "user",
49
+ "content": [
50
+ {"type": "text", "text": "What kind of flower is this?"},
51
+ {
52
+ "type": "image_url",
53
+ "image_url": {"url": "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2-VL/flowers.jpg"},
54
+ },
55
+ ],
56
+ }
57
+ )
58
+ result = client.chat.completions.create(messages=messages, model="test")
59
+ messages.append(result.choices[0].message)
60
+ print("Round 2:", result.choices[0].message.content)
61
+ # The image shows a cluster of forget-me-not flowers. Forget-me-nots are small ...
62
+
63
+
64
+ if __name__ == "__main__":
65
+ main()
scripts/api_example/test_toolcall.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ import os
17
+
18
+ from openai import OpenAI
19
+ from transformers.utils.versions import require_version
20
+
21
+
22
+ require_version("openai>=1.5.0", "To fix: pip install openai>=1.5.0")
23
+
24
+
25
+ def calculate_gpa(grades: list[str], hours: list[int]) -> float:
26
+ grade_to_score = {"A": 4, "B": 3, "C": 2}
27
+ total_score, total_hour = 0, 0
28
+ for grade, hour in zip(grades, hours):
29
+ total_score += grade_to_score[grade] * hour
30
+ total_hour += hour
31
+ return round(total_score / total_hour, 2)
32
+
33
+
34
+ def main():
35
+ client = OpenAI(
36
+ api_key="{}".format(os.getenv("API_KEY", "0")),
37
+ base_url="http://localhost:{}/v1".format(os.getenv("API_PORT", 8000)),
38
+ )
39
+ tools = [
40
+ {
41
+ "type": "function",
42
+ "function": {
43
+ "name": "calculate_gpa",
44
+ "description": "Calculate the Grade Point Average (GPA) based on grades and credit hours",
45
+ "parameters": {
46
+ "type": "object",
47
+ "properties": {
48
+ "grades": {"type": "array", "items": {"type": "string"}, "description": "The grades"},
49
+ "hours": {"type": "array", "items": {"type": "integer"}, "description": "The credit hours"},
50
+ },
51
+ "required": ["grades", "hours"],
52
+ },
53
+ },
54
+ }
55
+ ]
56
+ tool_map = {"calculate_gpa": calculate_gpa}
57
+
58
+ messages = []
59
+ messages.append({"role": "user", "content": "My grades are A, A, B, and C. The credit hours are 3, 4, 3, and 2."})
60
+ result = client.chat.completions.create(messages=messages, model="test", tools=tools)
61
+ if result.choices[0].message.tool_calls is None:
62
+ raise ValueError("Cannot retrieve function call from the response.")
63
+
64
+ messages.append(result.choices[0].message)
65
+ tool_call = result.choices[0].message.tool_calls[0].function
66
+ print(tool_call)
67
+ # Function(arguments='{"grades": ["A", "A", "B", "C"], "hours": [3, 4, 3, 2]}', name='calculate_gpa')
68
+ name, arguments = tool_call.name, json.loads(tool_call.arguments)
69
+ tool_result = tool_map[name](**arguments)
70
+ messages.append({"role": "tool", "content": json.dumps({"gpa": tool_result}, ensure_ascii=False)})
71
+ result = client.chat.completions.create(messages=messages, model="test", tools=tools)
72
+ print(result.choices[0].message.content)
73
+ # Based on the grades and credit hours you provided, your Grade Point Average (GPA) is 3.42.
74
+
75
+
76
+ if __name__ == "__main__":
77
+ main()
scripts/bench_qwen.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import os
16
+ from dataclasses import dataclass
17
+ from typing import Any
18
+
19
+ import fire
20
+ import torch
21
+ from peft import PeftModel
22
+ from torch.utils.data import Dataset
23
+ from transformers import DataCollatorForSeq2Seq, Qwen2_5_VLProcessor
24
+
25
+ from llamafactory.extras.constants import IGNORE_INDEX
26
+ from llamafactory.hparams import get_train_args
27
+ from llamafactory.model import load_model, load_tokenizer
28
+ from llamafactory.train.callbacks import LogCallback
29
+ from llamafactory.train.sft.trainer import CustomSeq2SeqTrainer
30
+
31
+
32
+ class DummyDataset(Dataset):
33
+ def __init__(self, size: int = 1000, seq_length: int = 1024, processor: Qwen2_5_VLProcessor = None):
34
+ self.size = size
35
+ self.seq_length = seq_length
36
+ self.vocab_size = 32768
37
+ self.processor = processor
38
+
39
+ image_token_num = 18 * 18 // (2 * 2)
40
+ image_t = 2
41
+
42
+ self.text_seqlen = seq_length // 4 # 25% text
43
+ video_seq_length = self.seq_length - self.text_seqlen - image_t * image_token_num
44
+ video_t = video_seq_length // image_token_num
45
+
46
+ self.image_size = [18 * 18 * image_t, 1176]
47
+ self.image_grid_thw = torch.tensor([[1, 18, 18]] * image_t, dtype=torch.long)
48
+ self.image_seqlen = image_t * image_token_num
49
+
50
+ self.video_size = [18 * 18 * video_t, 1176]
51
+ self.video_grid_thw = torch.tensor([[video_t, 18, 18]], dtype=torch.long)
52
+ self.video_seqlen = video_t * image_token_num
53
+
54
+ def __len__(self):
55
+ return self.size
56
+
57
+ def __getitem__(self, index: int):
58
+ input_ids = torch.randint(low=0, high=self.vocab_size, size=(self.seq_length,))
59
+ input_ids[: self.image_seqlen] = self.processor.image_token_id
60
+ input_ids[self.image_seqlen : self.image_seqlen + self.video_seqlen] = self.processor.video_token_id
61
+
62
+ attention_mask = torch.ones((self.seq_length,), dtype=torch.long)
63
+ labels = input_ids.clone()
64
+ labels[: self.image_seqlen + self.video_seqlen] = IGNORE_INDEX
65
+ pixel_values = torch.rand(self.image_size, dtype=torch.float32)
66
+ pixel_values_videos = torch.rand(self.video_size, dtype=torch.float32)
67
+ return {
68
+ "input_ids": input_ids,
69
+ "attention_mask": attention_mask,
70
+ "labels": labels,
71
+ "pixel_values": pixel_values,
72
+ "pixel_values_videos": pixel_values_videos,
73
+ "image_grid_thw": self.image_grid_thw,
74
+ "video_grid_thw": self.video_grid_thw,
75
+ }
76
+
77
+
78
+ @dataclass
79
+ class MultiModalDataCollatorForSeq2Seq(DataCollatorForSeq2Seq):
80
+ def __post_init__(self):
81
+ if isinstance(self.model, PeftModel):
82
+ self.model = self.model.base_model.model
83
+
84
+ if self.model is not None and hasattr(self.model, "get_rope_index"): # for qwen2vl mrope
85
+ self.get_rope_func = self.model.get_rope_index # transformers < 4.52.0 or qwen2.5 omni
86
+ elif self.model is not None and hasattr(self.model, "model") and hasattr(self.model.model, "get_rope_index"):
87
+ self.get_rope_func = self.model.model.get_rope_index # transformers >= 4.52.0
88
+ else:
89
+ self.get_rope_func = None
90
+
91
+ def __call__(self, features: list[dict[str, Any]]) -> dict[str, "torch.Tensor"]:
92
+ batch_pixel_values = [feature.pop("pixel_values") for feature in features]
93
+ batch_pixel_values_videos = [feature.pop("pixel_values_videos") for feature in features]
94
+ batch_image_grid_thw = [feature.pop("image_grid_thw") for feature in features]
95
+ batch_video_grid_thw = [feature.pop("video_grid_thw") for feature in features]
96
+
97
+ batch: dict[str, torch.Tensor] = super().__call__(features)
98
+
99
+ batch["pixel_values"] = torch.cat(batch_pixel_values, dim=0)
100
+ batch["pixel_values_videos"] = torch.cat(batch_pixel_values_videos, dim=0)
101
+ batch["image_grid_thw"] = torch.cat(batch_image_grid_thw, dim=0)
102
+ batch["video_grid_thw"] = torch.cat(batch_video_grid_thw, dim=0)
103
+
104
+ if self.get_rope_func is not None:
105
+ rope_index_kwargs = {
106
+ "input_ids": batch["input_ids"],
107
+ "image_grid_thw": batch["image_grid_thw"],
108
+ "video_grid_thw": batch["video_grid_thw"],
109
+ "attention_mask": (batch["attention_mask"] >= 1).float(),
110
+ }
111
+ batch["position_ids"], batch["rope_deltas"] = self.get_rope_func(**rope_index_kwargs)
112
+
113
+ if "position_ids" not in batch or batch["position_ids"].dim() != 3:
114
+ raise ValueError("Qwen2VL requires 3D position ids for mrope.")
115
+
116
+ return batch
117
+
118
+
119
+ def bench_qwen(
120
+ model_name_or_path: str = "Qwen/Qwen2-VL-7B-Instruct",
121
+ batch_size: int = 1,
122
+ seq_length: int = 2048,
123
+ liger_kernel: bool = False,
124
+ deepspeed_stage: int = 3,
125
+ ):
126
+ os.environ["LLAMABOARD_ENABLED"] = "true"
127
+ os.environ["LLAMABOARD_WORKDIR"] = "output/dummy_dir"
128
+ args = {
129
+ "model_name_or_path": model_name_or_path,
130
+ "enable_liger_kernel": liger_kernel,
131
+ "stage": "sft",
132
+ "do_train": True,
133
+ "finetuning_type": "full",
134
+ "dataset": "alpaca_en_demo",
135
+ "template": "qwen2_vl",
136
+ "cutoff_len": seq_length,
137
+ "output_dir": "output/dummy_dir",
138
+ "logging_steps": 10,
139
+ "save_strategy": "no",
140
+ "save_only_model": True,
141
+ "overwrite_output_dir": True,
142
+ "per_device_train_batch_size": batch_size,
143
+ "max_steps": 1000,
144
+ "bf16": True,
145
+ "include_num_input_tokens_seen": True,
146
+ "report_to": "none",
147
+ }
148
+ if deepspeed_stage in [2, 3]:
149
+ args["deepspeed"] = f"examples/deepspeed/ds_z{deepspeed_stage}_config.json"
150
+
151
+ model_args, _, training_args, finetuning_args, _ = get_train_args(args)
152
+ tokenizer_module = load_tokenizer(model_args)
153
+ tokenizer = tokenizer_module["tokenizer"]
154
+ trainset = DummyDataset(size=100000, seq_length=seq_length, processor=tokenizer_module["processor"])
155
+ model = load_model(tokenizer, model_args, finetuning_args, training_args.do_train)
156
+ data_collator = MultiModalDataCollatorForSeq2Seq(
157
+ tokenizer=tokenizer, model=model, pad_to_multiple_of=8, label_pad_token_id=IGNORE_INDEX
158
+ )
159
+
160
+ trainer = CustomSeq2SeqTrainer(
161
+ model=model,
162
+ args=training_args,
163
+ finetuning_args=finetuning_args,
164
+ data_collator=data_collator,
165
+ callbacks=[LogCallback()],
166
+ train_dataset=trainset,
167
+ **tokenizer_module,
168
+ )
169
+ trainer.train(resume_from_checkpoint=training_args.resume_from_checkpoint)
170
+
171
+
172
+ if __name__ == "__main__":
173
+ fire.Fire(bench_qwen)
scripts/convert_ckpt/llamafy_baichuan2.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ import os
17
+ from collections import OrderedDict
18
+ from typing import Any
19
+
20
+ import fire
21
+ import torch
22
+ from huggingface_hub import split_torch_state_dict_into_shards
23
+ from safetensors.torch import save_file
24
+ from tqdm import tqdm
25
+ from transformers.modeling_utils import SAFE_WEIGHTS_INDEX_NAME, SAFE_WEIGHTS_NAME, WEIGHTS_INDEX_NAME, WEIGHTS_NAME
26
+
27
+
28
+ CONFIG_NAME = "config.json"
29
+
30
+
31
+ def save_weight(input_dir: str, output_dir: str, shard_size: str, save_safetensors: bool):
32
+ baichuan2_state_dict: dict[str, torch.Tensor] = OrderedDict()
33
+ for filepath in tqdm(os.listdir(input_dir), desc="Load weights"):
34
+ if os.path.isfile(os.path.join(input_dir, filepath)) and filepath.endswith(".bin"):
35
+ shard_weight = torch.load(os.path.join(input_dir, filepath), map_location="cpu", weights_only=True)
36
+ baichuan2_state_dict.update(shard_weight)
37
+
38
+ llama_state_dict: dict[str, torch.Tensor] = OrderedDict()
39
+ for key, value in tqdm(baichuan2_state_dict.items(), desc="Convert format"):
40
+ if "W_pack" in key:
41
+ proj_size = value.size(0) // 3
42
+ llama_state_dict[key.replace("W_pack", "q_proj")] = value[:proj_size, :]
43
+ llama_state_dict[key.replace("W_pack", "k_proj")] = value[proj_size : 2 * proj_size, :]
44
+ llama_state_dict[key.replace("W_pack", "v_proj")] = value[2 * proj_size :, :]
45
+ elif "lm_head" in key:
46
+ llama_state_dict[key] = torch.nn.functional.normalize(value)
47
+ else:
48
+ llama_state_dict[key] = value
49
+
50
+ weights_name = SAFE_WEIGHTS_NAME if save_safetensors else WEIGHTS_NAME
51
+ filename_pattern = weights_name.replace(".bin", "{suffix}.bin").replace(".safetensors", "{suffix}.safetensors")
52
+ state_dict_split = split_torch_state_dict_into_shards(
53
+ llama_state_dict, filename_pattern=filename_pattern, max_shard_size=shard_size
54
+ )
55
+ for shard_file, tensors in tqdm(state_dict_split.filename_to_tensors.items(), desc="Save weights"):
56
+ shard = {tensor: llama_state_dict[tensor].contiguous() for tensor in tensors}
57
+ if save_safetensors:
58
+ save_file(shard, os.path.join(output_dir, shard_file), metadata={"format": "pt"})
59
+ else:
60
+ torch.save(shard, os.path.join(output_dir, shard_file))
61
+
62
+ if not state_dict_split.is_sharded:
63
+ print(f"Model weights saved in {os.path.join(output_dir, weights_name)}.")
64
+ else:
65
+ index = {
66
+ "metadata": state_dict_split.metadata,
67
+ "weight_map": state_dict_split.tensor_to_filename,
68
+ }
69
+ index_name = SAFE_WEIGHTS_INDEX_NAME if save_safetensors else WEIGHTS_INDEX_NAME
70
+ with open(os.path.join(output_dir, index_name), "w", encoding="utf-8") as f:
71
+ json.dump(index, f, indent=2, sort_keys=True)
72
+
73
+ print(f"Model weights saved in {output_dir}.")
74
+
75
+
76
+ def save_config(input_dir: str, output_dir: str):
77
+ with open(os.path.join(input_dir, CONFIG_NAME), encoding="utf-8") as f:
78
+ llama2_config_dict: dict[str, Any] = json.load(f)
79
+
80
+ llama2_config_dict["architectures"] = ["LlamaForCausalLM"]
81
+ llama2_config_dict.pop("auto_map", None)
82
+ llama2_config_dict.pop("tokenizer_class", None)
83
+ llama2_config_dict["model_type"] = "llama"
84
+
85
+ with open(os.path.join(output_dir, CONFIG_NAME), "w", encoding="utf-8") as f:
86
+ json.dump(llama2_config_dict, f, indent=2)
87
+
88
+ print(f"Model config saved in {os.path.join(output_dir, CONFIG_NAME)}")
89
+
90
+
91
+ def llamafy_baichuan2(
92
+ input_dir: str,
93
+ output_dir: str,
94
+ shard_size: str = "2GB",
95
+ save_safetensors: bool = True,
96
+ ):
97
+ r"""Convert the Baichuan2-7B model in the same format as LLaMA2-7B.
98
+
99
+ Usage: python llamafy_baichuan2.py --input_dir input --output_dir output
100
+ Converted model: https://huggingface.co/hiyouga/Baichuan2-7B-Base-LLaMAfied
101
+ """
102
+ try:
103
+ os.makedirs(output_dir, exist_ok=False)
104
+ except Exception as e:
105
+ raise print("Output dir already exists", e)
106
+
107
+ save_weight(input_dir, output_dir, shard_size, save_safetensors)
108
+ save_config(input_dir, output_dir)
109
+
110
+
111
+ if __name__ == "__main__":
112
+ fire.Fire(llamafy_baichuan2)
scripts/convert_ckpt/llamafy_qwen.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ import os
17
+ from collections import OrderedDict
18
+ from typing import Any
19
+
20
+ import fire
21
+ import torch
22
+ from huggingface_hub import split_torch_state_dict_into_shards
23
+ from safetensors import safe_open
24
+ from safetensors.torch import save_file
25
+ from tqdm import tqdm
26
+ from transformers.modeling_utils import SAFE_WEIGHTS_INDEX_NAME, SAFE_WEIGHTS_NAME, WEIGHTS_INDEX_NAME, WEIGHTS_NAME
27
+ from transformers.utils import check_min_version
28
+
29
+
30
+ try:
31
+ check_min_version("4.34.0")
32
+ except Exception:
33
+ raise ValueError("Please upgrade `transformers` to 4.34.0")
34
+
35
+
36
+ CONFIG_NAME = "config.json"
37
+
38
+
39
+ def save_weight(input_dir: str, output_dir: str, shard_size: str, save_safetensors: bool) -> str:
40
+ qwen_state_dict: dict[str, torch.Tensor] = OrderedDict()
41
+ for filepath in tqdm(os.listdir(input_dir), desc="Load weights"):
42
+ if os.path.isfile(os.path.join(input_dir, filepath)) and filepath.endswith(".safetensors"):
43
+ with safe_open(os.path.join(input_dir, filepath), framework="pt", device="cpu") as f:
44
+ for key in f.keys():
45
+ qwen_state_dict[key] = f.get_tensor(key)
46
+
47
+ llama_state_dict: dict[str, torch.Tensor] = OrderedDict()
48
+ torch_dtype = None
49
+ for key, value in tqdm(qwen_state_dict.items(), desc="Convert format"):
50
+ if torch_dtype is None:
51
+ torch_dtype = value.dtype
52
+ if "wte" in key:
53
+ llama_state_dict["model.embed_tokens.weight"] = value
54
+ elif "ln_f" in key:
55
+ llama_state_dict["model.norm.weight"] = value
56
+ else:
57
+ key = key.replace("transformer.h", "model.layers")
58
+ if "attn.c_attn" in key:
59
+ proj_size = value.size(0) // 3
60
+ llama_state_dict[key.replace("attn.c_attn", "self_attn.q_proj")] = value[:proj_size, ...]
61
+ llama_state_dict[key.replace("attn.c_attn", "self_attn.k_proj")] = value[
62
+ proj_size : 2 * proj_size, ...
63
+ ]
64
+ llama_state_dict[key.replace("attn.c_attn", "self_attn.v_proj")] = value[2 * proj_size :, ...]
65
+ elif "attn.c_proj" in key:
66
+ llama_state_dict[key.replace("attn.c_proj", "self_attn.o_proj")] = value
67
+ llama_state_dict[key.replace("attn.c_proj.weight", "self_attn.o_proj.bias")] = torch.zeros_like(
68
+ value[:, 0]
69
+ ).squeeze()
70
+ elif "ln_1" in key:
71
+ llama_state_dict[key.replace("ln_1", "input_layernorm")] = value
72
+ elif "ln_2" in key:
73
+ llama_state_dict[key.replace("ln_2", "post_attention_layernorm")] = value
74
+ elif "mlp.w1" in key:
75
+ llama_state_dict[key.replace("mlp.w1", "mlp.up_proj")] = value
76
+ elif "mlp.w2" in key:
77
+ llama_state_dict[key.replace("mlp.w2", "mlp.gate_proj")] = value
78
+ elif "mlp.c_proj" in key:
79
+ llama_state_dict[key.replace("mlp.c_proj", "mlp.down_proj")] = value
80
+ elif "lm_head" in key:
81
+ llama_state_dict[key] = value
82
+ else:
83
+ raise KeyError(f"Unable to process key {key}")
84
+
85
+ weights_name = SAFE_WEIGHTS_NAME if save_safetensors else WEIGHTS_NAME
86
+ filename_pattern = weights_name.replace(".bin", "{suffix}.bin").replace(".safetensors", "{suffix}.safetensors")
87
+ state_dict_split = split_torch_state_dict_into_shards(
88
+ llama_state_dict, filename_pattern=filename_pattern, max_shard_size=shard_size
89
+ )
90
+ for shard_file, tensors in tqdm(state_dict_split.filename_to_tensors.items(), desc="Save weights"):
91
+ shard = {tensor: llama_state_dict[tensor].contiguous() for tensor in tensors}
92
+ if save_safetensors:
93
+ save_file(shard, os.path.join(output_dir, shard_file), metadata={"format": "pt"})
94
+ else:
95
+ torch.save(shard, os.path.join(output_dir, shard_file))
96
+
97
+ if not state_dict_split.is_sharded:
98
+ print(f"Model weights saved in {os.path.join(output_dir, weights_name)}.")
99
+ else:
100
+ index = {
101
+ "metadata": state_dict_split.metadata,
102
+ "weight_map": state_dict_split.tensor_to_filename,
103
+ }
104
+ index_name = SAFE_WEIGHTS_INDEX_NAME if save_safetensors else WEIGHTS_INDEX_NAME
105
+ with open(os.path.join(output_dir, index_name), "w", encoding="utf-8") as f:
106
+ json.dump(index, f, indent=2, sort_keys=True)
107
+
108
+ print(f"Model weights saved in {output_dir}.")
109
+
110
+ return str(torch_dtype).replace("torch.", "")
111
+
112
+
113
+ def save_config(input_dir: str, output_dir: str, torch_dtype: str):
114
+ with open(os.path.join(input_dir, CONFIG_NAME), encoding="utf-8") as f:
115
+ qwen_config_dict: dict[str, Any] = json.load(f)
116
+
117
+ llama2_config_dict: dict[str, Any] = OrderedDict()
118
+ llama2_config_dict["architectures"] = ["LlamaForCausalLM"]
119
+ llama2_config_dict["hidden_act"] = "silu"
120
+ llama2_config_dict["hidden_size"] = qwen_config_dict["hidden_size"]
121
+ llama2_config_dict["initializer_range"] = qwen_config_dict["initializer_range"]
122
+ llama2_config_dict["intermediate_size"] = qwen_config_dict["intermediate_size"] // 2
123
+ llama2_config_dict["max_position_embeddings"] = qwen_config_dict["max_position_embeddings"]
124
+ llama2_config_dict["model_type"] = "llama"
125
+ llama2_config_dict["num_attention_heads"] = qwen_config_dict["num_attention_heads"]
126
+ llama2_config_dict["num_hidden_layers"] = qwen_config_dict["num_hidden_layers"]
127
+ llama2_config_dict["num_key_value_heads"] = qwen_config_dict["hidden_size"] // qwen_config_dict["kv_channels"]
128
+ llama2_config_dict["pretraining_tp"] = 1
129
+ llama2_config_dict["rms_norm_eps"] = qwen_config_dict["layer_norm_epsilon"]
130
+ llama2_config_dict["rope_scaling"] = None
131
+ llama2_config_dict["tie_word_embeddings"] = qwen_config_dict["tie_word_embeddings"]
132
+ llama2_config_dict["torch_dtype"] = torch_dtype
133
+ llama2_config_dict["transformers_version"] = "4.34.0"
134
+ llama2_config_dict["use_cache"] = True
135
+ llama2_config_dict["vocab_size"] = qwen_config_dict["vocab_size"]
136
+ llama2_config_dict["attention_bias"] = True
137
+
138
+ with open(os.path.join(output_dir, CONFIG_NAME), "w", encoding="utf-8") as f:
139
+ json.dump(llama2_config_dict, f, indent=2)
140
+
141
+ print(f"Model config saved in {os.path.join(output_dir, CONFIG_NAME)}")
142
+
143
+
144
+ def llamafy_qwen(
145
+ input_dir: str,
146
+ output_dir: str,
147
+ shard_size: str = "2GB",
148
+ save_safetensors: bool = False,
149
+ ):
150
+ r"""Convert the Qwen models in the same format as LLaMA2.
151
+
152
+ Usage: python llamafy_qwen.py --input_dir input --output_dir output
153
+ Converted model: https://huggingface.co/hiyouga/Qwen-14B-Chat-LLaMAfied
154
+ """
155
+ try:
156
+ os.makedirs(output_dir, exist_ok=False)
157
+ except Exception as e:
158
+ raise print("Output dir already exists", e)
159
+
160
+ torch_dtype = save_weight(input_dir, output_dir, shard_size, save_safetensors)
161
+ save_config(input_dir, output_dir, torch_dtype)
162
+
163
+
164
+ if __name__ == "__main__":
165
+ fire.Fire(llamafy_qwen)
scripts/convert_ckpt/tiny_llama4.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from transformers import Llama4Config, Llama4ForConditionalGeneration, Llama4TextConfig, Llama4VisionConfig
16
+
17
+
18
+ if __name__ == "__main__":
19
+ vision_config = Llama4VisionConfig(
20
+ hidden_size=1408,
21
+ image_size=336,
22
+ intermediate_size=5632,
23
+ num_attention_heads=16,
24
+ num_hidden_layers=4,
25
+ vision_output_dim=4096,
26
+ )
27
+ text_config = Llama4TextConfig(
28
+ hidden_size=512,
29
+ intermediate_size=1024,
30
+ intermediate_size_mlp=1024,
31
+ num_hidden_layers=4,
32
+ num_attention_heads=8,
33
+ num_key_value_heads=2,
34
+ head_dim=512 // 8,
35
+ num_local_experts=2,
36
+ )
37
+ config = Llama4Config(vision_config=vision_config, text_config=text_config)
38
+ model = Llama4ForConditionalGeneration._from_config(config)
39
+ model.save_pretrained("tiny-llama4")
scripts/convert_ckpt/tiny_qwen3.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from transformers import AutoTokenizer, Qwen3Config, Qwen3ForCausalLM
16
+
17
+
18
+ if __name__ == "__main__":
19
+ tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-4B-Instruct-2507")
20
+ config = Qwen3Config(
21
+ hidden_size=1408,
22
+ image_size=336,
23
+ intermediate_size=5632,
24
+ num_attention_heads=16,
25
+ num_hidden_layers=4,
26
+ vision_output_dim=4096,
27
+ )
28
+ model = Qwen3ForCausalLM.from_config(config)
29
+ model.save_pretrained("tiny-qwen3")
30
+ tokenizer.save_pretrained("tiny-qwen3")
31
+ model.push_to_hub("llamafactory/tiny-random-qwen3")
32
+ tokenizer.push_to_hub("llamafactory/tiny-random-qwen3")
scripts/dcp2hf.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Convert a DCP checkpoint to HuggingFace model format.
16
+
17
+ Usage:
18
+ python scripts/dcp2hf.py convert --dcp_path=/path/to/dcp --hf_path=/path/to/hf --config_path=/path/to/config
19
+
20
+ Arguments:
21
+ dcp_path: Path to the DCP checkpoint directory.
22
+ hf_path: Output path (directory) for HuggingFace model.
23
+ config_path: Path to the HuggingFace model directory containing config.json.
24
+ """
25
+
26
+ import fire
27
+ import torch
28
+ import torch.distributed.checkpoint as dcp
29
+ import transformers
30
+ from transformers import AutoConfig
31
+
32
+
33
+ def convert(dcp_path: str, hf_path: str, config_path: str) -> None:
34
+ """Convert DCP model weights to HF.
35
+
36
+ Note: this script is used to convert a DCP checkpoint to HuggingFace model format,
37
+ it will just convert the DCP checkpoint to a HuggingFace model format, for the tokenizer,
38
+ you may need to copy from the original model.
39
+
40
+ Args:
41
+ dcp_path: DCP checkpoint directory.
42
+ hf_path: Output path (directory) for HuggingFace model.
43
+ config_path: Path to the HuggingFace model directory containing config.json.
44
+ """
45
+ if not dcp_path or not hf_path or not config_path:
46
+ raise ValueError("All 'dcp_path', 'hf_path', and 'config_path' are required.")
47
+
48
+ print(f"Loading config from {config_path}...")
49
+ config = AutoConfig.from_pretrained(config_path)
50
+ architectures = getattr(config, "architectures", [])
51
+ if architectures:
52
+ model_cls = getattr(transformers, architectures[0], transformers.AutoModelForCausalLM)
53
+ else:
54
+ model_cls = transformers.AutoModelForCausalLM
55
+
56
+ print("Initializing model on CPU...")
57
+ model = model_cls(config).to(torch.bfloat16)
58
+
59
+ print(f"Loading DCP from {dcp_path}...")
60
+ state_dict = model.state_dict()
61
+ dcp.load(state_dict, checkpoint_id=dcp_path)
62
+ model.load_state_dict(state_dict)
63
+
64
+ print(f"Saving to HF format at {hf_path}...")
65
+ model.save_pretrained(hf_path)
66
+ config.save_pretrained(hf_path)
67
+ print("Done!")
68
+
69
+
70
+ def help() -> None:
71
+ """Show help message."""
72
+ print(__doc__)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ fire.Fire({"convert": convert, "help": help, "--convert": convert})
scripts/eval_bleu_rouge.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ import logging
17
+ import time
18
+
19
+ import fire
20
+ from datasets import load_dataset
21
+
22
+
23
+ try:
24
+ import jieba # type: ignore
25
+ from nltk.translate.bleu_score import SmoothingFunction, sentence_bleu # type: ignore
26
+ from rouge_chinese import Rouge # type: ignore
27
+
28
+ jieba.setLogLevel(logging.CRITICAL)
29
+ jieba.initialize()
30
+ except ImportError:
31
+ print("Please install llamafactory with `pip install -r requirements/metrics.txt`.")
32
+ raise
33
+
34
+
35
+ def compute_metrics(sample):
36
+ hypothesis = list(jieba.cut(sample["predict"]))
37
+ reference = list(jieba.cut(sample["label"]))
38
+
39
+ bleu_score = sentence_bleu(
40
+ [list(sample["label"])],
41
+ list(sample["predict"]),
42
+ smoothing_function=SmoothingFunction().method3,
43
+ )
44
+
45
+ if len(" ".join(hypothesis).split()) == 0 or len(" ".join(reference).split()) == 0:
46
+ result = {"rouge-1": {"f": 0.0}, "rouge-2": {"f": 0.0}, "rouge-l": {"f": 0.0}}
47
+ else:
48
+ rouge = Rouge()
49
+ scores = rouge.get_scores(" ".join(hypothesis), " ".join(reference))
50
+ result = scores[0]
51
+
52
+ metric_result = {}
53
+ for k, v in result.items():
54
+ metric_result[k] = round(v["f"] * 100, 4)
55
+
56
+ metric_result["bleu-4"] = round(bleu_score * 100, 4)
57
+
58
+ return metric_result
59
+
60
+
61
+ def main(filename: str):
62
+ start_time = time.time()
63
+ dataset = load_dataset("json", data_files=filename, split="train")
64
+ dataset = dataset.map(compute_metrics, num_proc=8, remove_columns=dataset.column_names)
65
+ score_dict = dataset.to_dict()
66
+
67
+ average_score = {}
68
+ for task, scores in sorted(score_dict.items(), key=lambda x: x[0]):
69
+ print(f"{task}: {sum(scores) / len(scores):.4f}")
70
+ average_score[task] = sum(scores) / len(scores)
71
+
72
+ with open("predictions_score.json", "w", encoding="utf-8") as f:
73
+ json.dump(average_score, f, indent=4)
74
+
75
+ print(f"\nDone in {time.time() - start_time:.3f}s.\nScore file saved to predictions_score.json")
76
+
77
+
78
+ if __name__ == "__main__":
79
+ fire.Fire(main)
scripts/hf2dcp.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Convert a HuggingFace model to DCP checkpoint format.
16
+
17
+ Usage:
18
+ python scripts/hf2dcp.py convert --hf_path=/path/to/hf --dcp_path=/path/to/dcp
19
+
20
+ Arguments:
21
+ hf_path: Path to the HuggingFace model directory.
22
+ dcp_path: Output path (directory) for DCP checkpoint.
23
+ """
24
+
25
+ import fire
26
+ import torch
27
+ import torch.distributed.checkpoint as dcp
28
+ import transformers
29
+ from transformers import AutoConfig
30
+
31
+
32
+ def convert(hf_path: str, dcp_path: str) -> None:
33
+ """Convert HF model weights to DCP.
34
+
35
+ Args:
36
+ hf_path: HuggingFace model directory.
37
+ dcp_path: Output path (directory) for DCP checkpoint.
38
+ """
39
+ if not hf_path or not dcp_path:
40
+ raise ValueError("Both 'hf_path' and 'dcp_path' are required.")
41
+
42
+ print(f"Loading HF model from {hf_path}...")
43
+ config = AutoConfig.from_pretrained(hf_path)
44
+ architectures = getattr(config, "architectures", [])
45
+ if architectures:
46
+ model_cls = getattr(transformers, architectures[0], transformers.AutoModelForCausalLM)
47
+ else:
48
+ model_cls = transformers.AutoModelForCausalLM
49
+
50
+ model = model_cls.from_pretrained(hf_path, device_map="cpu", torch_dtype=torch.bfloat16)
51
+
52
+ print(f"Saving to DCP format at {dcp_path}...")
53
+ dcp.save(model.state_dict(), checkpoint_id=dcp_path)
54
+ print("Done!")
55
+
56
+
57
+ def help() -> None:
58
+ """Show help message."""
59
+ print(__doc__)
60
+
61
+
62
+ if __name__ == "__main__":
63
+ fire.Fire({"convert": convert, "help": help, "--convert": convert})
scripts/llama_pro.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 Tencent Inc. and the LlamaFactory team.
2
+ #
3
+ # This code is inspired by the Tencent's LLaMA-Pro library.
4
+ # https://github.com/TencentARC/LLaMA-Pro/blob/main/scripts/block_expansion.py
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ import json
19
+ import os
20
+ from collections import OrderedDict
21
+ from typing import TYPE_CHECKING
22
+
23
+ import fire
24
+ import torch
25
+ from huggingface_hub import split_torch_state_dict_into_shards
26
+ from safetensors.torch import save_file
27
+ from tqdm import tqdm
28
+ from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer, PreTrainedModel
29
+ from transformers.modeling_utils import SAFE_WEIGHTS_INDEX_NAME, SAFE_WEIGHTS_NAME, WEIGHTS_INDEX_NAME, WEIGHTS_NAME
30
+
31
+
32
+ if TYPE_CHECKING:
33
+ from transformers import PretrainedConfig
34
+
35
+
36
+ def change_name(name: str, old_index: int, new_index: int) -> str:
37
+ return name.replace(f".{old_index:d}.", f".{new_index:d}.")
38
+
39
+
40
+ def block_expansion(
41
+ model_name_or_path: str,
42
+ output_dir: str,
43
+ num_expand: int,
44
+ shard_size: str = "5GB",
45
+ save_safetensors: bool = True,
46
+ ):
47
+ r"""Perform block expansion for LLaMA, Mistral, Qwen2 or Yi models.
48
+
49
+ Usage: python llama_pro.py --model_name_or_path meta-llama/Llama-2-7b-hf --output_dir llama2_pro --num_expand 8
50
+ """
51
+ config: PretrainedConfig = AutoConfig.from_pretrained(model_name_or_path, trust_remote_code=True)
52
+ num_layers = getattr(config, "num_hidden_layers")
53
+ if num_layers % num_expand != 0:
54
+ raise ValueError(f"`num_layers` {num_layers} should be divisible by `num_expand` {num_expand}.")
55
+
56
+ setattr(config, "num_hidden_layers", num_layers + num_expand)
57
+ config.save_pretrained(output_dir)
58
+
59
+ tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True)
60
+ tokenizer.save_pretrained(output_dir)
61
+
62
+ print(f"Expanding model of {num_layers} layers to {num_layers + num_expand} layers.")
63
+ model = AutoModelForCausalLM.from_pretrained(
64
+ model_name_or_path, torch_dtype="auto", device_map="cpu", trust_remote_code=True, low_cpu_mem_usage=True
65
+ )
66
+ assert isinstance(model, PreTrainedModel) # type hint
67
+ if save_safetensors and getattr(model.config, "tie_word_embeddings", False):
68
+ del model.lm_head # safetensors does not allow shared weights
69
+
70
+ split = num_layers // num_expand
71
+ layer_cnt = 0
72
+ state_dict = model.state_dict()
73
+ output_state_dict: dict[str, torch.Tensor] = OrderedDict()
74
+ for i in range(num_layers):
75
+ for key, value in state_dict.items():
76
+ if f".{i:d}." in key:
77
+ output_state_dict[change_name(key, i, layer_cnt)] = value
78
+
79
+ print(f"Add layer {layer_cnt} copied from layer {i}.")
80
+ layer_cnt += 1
81
+ if (i + 1) % split == 0:
82
+ for key, value in state_dict.items():
83
+ if f".{i:d}." in key:
84
+ if "down_proj" in key or "o_proj" in key:
85
+ output_state_dict[change_name(key, i, layer_cnt)] = torch.zeros_like(value)
86
+ else:
87
+ output_state_dict[change_name(key, i, layer_cnt)] = torch.clone(value)
88
+
89
+ print(f"Add layer {layer_cnt} expanded from layer {i}.")
90
+ layer_cnt += 1
91
+
92
+ for key, value in state_dict.items():
93
+ if key not in output_state_dict:
94
+ output_state_dict[key] = value
95
+
96
+ weights_name = SAFE_WEIGHTS_NAME if save_safetensors else WEIGHTS_NAME
97
+ filename_pattern = weights_name.replace(".bin", "{suffix}.bin").replace(".safetensors", "{suffix}.safetensors")
98
+ state_dict_split = split_torch_state_dict_into_shards(
99
+ output_state_dict, filename_pattern=filename_pattern, max_shard_size=shard_size
100
+ )
101
+ for shard_file, tensors in tqdm(state_dict_split.filename_to_tensors.items(), desc="Save weights"):
102
+ shard = {tensor: output_state_dict[tensor].contiguous() for tensor in tensors}
103
+ if save_safetensors:
104
+ save_file(shard, os.path.join(output_dir, shard_file), metadata={"format": "pt"})
105
+ else:
106
+ torch.save(shard, os.path.join(output_dir, shard_file))
107
+
108
+ if not state_dict_split.is_sharded:
109
+ print(f"Model weights saved in {os.path.join(output_dir, weights_name)}.")
110
+ else:
111
+ index = {
112
+ "metadata": state_dict_split.metadata,
113
+ "weight_map": state_dict_split.tensor_to_filename,
114
+ }
115
+ index_name = SAFE_WEIGHTS_INDEX_NAME if save_safetensors else WEIGHTS_INDEX_NAME
116
+ with open(os.path.join(output_dir, index_name), "w", encoding="utf-8") as f:
117
+ json.dump(index, f, indent=2, sort_keys=True)
118
+
119
+ print(f"Model weights saved in {output_dir}.")
120
+
121
+ print("- Fine-tune this model with:")
122
+ print(f"model_name_or_path: {output_dir}")
123
+ print("finetuning_type: freeze")
124
+ print(f"freeze_trainable_layers: {num_expand}")
125
+ print("use_llama_pro: true")
126
+
127
+
128
+ if __name__ == "__main__":
129
+ fire.Fire(block_expansion)
scripts/loftq_init.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 HuggingFace Inc. and the LlamaFactory team.
2
+ #
3
+ # This code is based on the HuggingFace's PEFT library.
4
+ # https://github.com/huggingface/peft/blob/v0.10.0/examples/loftq_finetuning/quantize_save_load.py
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ import os
19
+ from typing import TYPE_CHECKING
20
+
21
+ import fire
22
+ from peft import LoftQConfig, LoraConfig, TaskType, get_peft_model
23
+ from transformers import AutoModelForCausalLM, AutoTokenizer
24
+
25
+
26
+ if TYPE_CHECKING:
27
+ from transformers import PreTrainedModel
28
+
29
+
30
+ def quantize_loftq(
31
+ model_name_or_path: str,
32
+ output_dir: str,
33
+ loftq_bits: int = 4,
34
+ loftq_iter: int = 4,
35
+ lora_alpha: int = None,
36
+ lora_rank: int = 16,
37
+ lora_dropout: float = 0,
38
+ lora_target: tuple = ("q_proj", "v_proj"),
39
+ save_safetensors: bool = True,
40
+ ):
41
+ r"""Initialize LoRA weights with LoRA-fine-tuning-aware Quantization (LoftQ).
42
+
43
+ Usage: python loftq_init.py --model_name_or_path path_to_model --output_dir output_dir
44
+ """
45
+ if isinstance(lora_target, str):
46
+ lora_target = [name.strip() for name in lora_target.split(",")]
47
+
48
+ tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True)
49
+ model = AutoModelForCausalLM.from_pretrained(model_name_or_path, trust_remote_code=True, torch_dtype="auto")
50
+
51
+ loftq_config = LoftQConfig(loftq_bits=loftq_bits, loftq_iter=loftq_iter)
52
+ lora_config = LoraConfig(
53
+ task_type=TaskType.CAUSAL_LM,
54
+ inference_mode=True,
55
+ r=lora_rank,
56
+ lora_alpha=lora_alpha if lora_alpha is not None else lora_rank * 2,
57
+ lora_dropout=lora_dropout,
58
+ target_modules=lora_target,
59
+ init_lora_weights="loftq",
60
+ loftq_config=loftq_config,
61
+ )
62
+
63
+ # Init LoftQ model
64
+ print("Initializing LoftQ weights, it may be take several minutes, wait patiently.")
65
+ peft_model = get_peft_model(model, lora_config)
66
+ loftq_dir = os.path.join(output_dir, "loftq_init")
67
+
68
+ # Save LoftQ model
69
+ setattr(peft_model.peft_config["default"], "base_model_name_or_path", os.path.abspath(output_dir))
70
+ setattr(peft_model.peft_config["default"], "init_lora_weights", True) # don't apply loftq again
71
+ peft_model.save_pretrained(loftq_dir, safe_serialization=save_safetensors)
72
+ print(f"Adapter weights saved in {loftq_dir}")
73
+
74
+ # Save base model
75
+ base_model: PreTrainedModel = peft_model.unload()
76
+ base_model.save_pretrained(output_dir, safe_serialization=save_safetensors)
77
+ tokenizer.save_pretrained(output_dir)
78
+ print(f"Model weights saved in {output_dir}")
79
+
80
+ print("- Fine-tune this model with:")
81
+ print(f"model_name_or_path: {output_dir}")
82
+ print(f"adapter_name_or_path: {loftq_dir}")
83
+ print("finetuning_type: lora")
84
+ print(f"quantization_bit: {loftq_bits}")
85
+
86
+
87
+ if __name__ == "__main__":
88
+ fire.Fire(quantize_loftq)
scripts/megatron_merge.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the ROLL team and the LlamaFactory team.
2
+ #
3
+ # This code is modified from the ROLL library.
4
+ # https://github.com/alibaba/ROLL/blob/main/mcore_adapter/tools/convert.py
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ import os
19
+
20
+ import fire
21
+ import torch
22
+ from mcore_adapter.models.converter.post_converter import convert_checkpoint_to_hf, convert_checkpoint_to_mca
23
+ from mcore_adapter.training_args import DistributingParallelArguments
24
+ from mcore_adapter.utils import get_logger
25
+ from transformers import AutoConfig
26
+
27
+
28
+ logger = get_logger(__name__)
29
+
30
+
31
+ def convert_mca_to_hf(
32
+ checkpoint_path: str,
33
+ output_path: str = "./output",
34
+ bf16: bool = False,
35
+ fp16: bool = False,
36
+ convert_model_max_length: int | None = None,
37
+ ):
38
+ """Convert megatron checkpoint to HuggingFace format.
39
+
40
+ Args:
41
+ checkpoint_path: Path to the checkpoint to convert
42
+ output_path: Path to save the converted checkpoint
43
+ bf16: Use bfloat16 precision
44
+ fp16: Use float16 precision
45
+ convert_model_max_length: Change the model_max_length in hf config.json
46
+ """
47
+ if bf16 and fp16:
48
+ raise ValueError("bf16 and fp16 cannot be both True.")
49
+
50
+ torch_dtype = None
51
+ if bf16:
52
+ torch_dtype = torch.bfloat16
53
+ elif fp16:
54
+ torch_dtype = torch.float16
55
+
56
+ convert_checkpoint_to_hf(checkpoint_path, output_path, torch_dtype=torch_dtype)
57
+
58
+ if convert_model_max_length is not None:
59
+ config = AutoConfig.from_pretrained(output_path, trust_remote_code=True)
60
+ config.model_max_length = convert_model_max_length
61
+ config.save_pretrained(output_path)
62
+
63
+
64
+ def convert(
65
+ checkpoint_path: str,
66
+ output_path: str = "./output",
67
+ bf16: bool = False,
68
+ fp16: bool = False,
69
+ convert_model_max_length: int | None = None,
70
+ tensor_model_parallel_size: int = 1,
71
+ pipeline_model_parallel_size: int = 1,
72
+ expert_model_parallel_size: int = 1,
73
+ virtual_pipeline_model_parallel_size: int | None = None,
74
+ moe_grouped_gemm: bool | None = None,
75
+ ):
76
+ """Convert checkpoint between MCA and HuggingFace formats.
77
+
78
+ Args:
79
+ checkpoint_path: Path to the checkpoint to convert
80
+ output_path: Path to save the converted checkpoint
81
+ bf16: Use bfloat16 precision
82
+ fp16: Use float16 precision
83
+ convert_model_max_length: Change the model_max_length in hf config.json
84
+ tensor_model_parallel_size: Tensor model parallel size
85
+ pipeline_model_parallel_size: Pipeline model parallel size
86
+ expert_model_parallel_size: Expert model parallel size
87
+ virtual_pipeline_model_parallel_size: Virtual pipeline model parallel size
88
+ moe_grouped_gemm: Use grouped gemm for MoE experts. When enabled, expert
89
+ weights are stored in a flattened format (linear_fc1.weight0, weight1, ...)
90
+ rather than per-expert format (local_experts.0.linear_fc1.weight, ...).
91
+ Must match the format used when saving the checkpoint.
92
+ """
93
+ if bf16 and fp16:
94
+ raise ValueError("bf16 and fp16 cannot be both True.")
95
+
96
+ mca_config_path = os.path.join(checkpoint_path, "mca_config.json")
97
+ from_mca = os.path.exists(mca_config_path)
98
+
99
+ if not from_mca:
100
+ dist_args = DistributingParallelArguments(
101
+ tensor_model_parallel_size=tensor_model_parallel_size,
102
+ pipeline_model_parallel_size=pipeline_model_parallel_size,
103
+ expert_model_parallel_size=expert_model_parallel_size,
104
+ virtual_pipeline_model_parallel_size=virtual_pipeline_model_parallel_size,
105
+ moe_grouped_gemm=moe_grouped_gemm,
106
+ transformer_impl="transformer_engine", # hard code here since we default using te for training
107
+ )
108
+ convert_checkpoint_to_mca(
109
+ checkpoint_path,
110
+ output_path,
111
+ dist_args,
112
+ bf16=bf16,
113
+ fp16=fp16,
114
+ )
115
+ else:
116
+ convert_mca_to_hf(
117
+ checkpoint_path=checkpoint_path,
118
+ output_path=output_path,
119
+ bf16=bf16,
120
+ fp16=fp16,
121
+ convert_model_max_length=convert_model_max_length,
122
+ )
123
+
124
+
125
+ def main():
126
+ fire.Fire(convert)
127
+
128
+
129
+ if __name__ == "__main__":
130
+ main()
scripts/pissa_init.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 HuggingFace Inc. and the LlamaFactory team.
2
+ #
3
+ # This code is based on the HuggingFace's PEFT library.
4
+ # https://github.com/huggingface/peft/blob/v0.11.0/examples/pissa_finetuning/preprocess.py
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ import os
19
+ from typing import TYPE_CHECKING
20
+
21
+ import fire
22
+ from peft import LoraConfig, TaskType, get_peft_model
23
+ from transformers import AutoModelForCausalLM, AutoTokenizer
24
+
25
+
26
+ if TYPE_CHECKING:
27
+ from transformers import PreTrainedModel
28
+
29
+
30
+ def quantize_pissa(
31
+ model_name_or_path: str,
32
+ output_dir: str,
33
+ pissa_iter: int = 16,
34
+ lora_alpha: int = None,
35
+ lora_rank: int = 16,
36
+ lora_dropout: float = 0,
37
+ lora_target: tuple = ("q_proj", "v_proj"),
38
+ save_safetensors: bool = True,
39
+ ):
40
+ r"""Initialize LoRA weights with Principal Singular values and Singular vectors Adaptation (PiSSA).
41
+
42
+ Usage: python pissa_init.py --model_name_or_path path_to_model --output_dir output_dir
43
+ """
44
+ if isinstance(lora_target, str):
45
+ lora_target = [name.strip() for name in lora_target.split(",")]
46
+
47
+ tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True)
48
+ model = AutoModelForCausalLM.from_pretrained(model_name_or_path, trust_remote_code=True, torch_dtype="auto")
49
+
50
+ lora_config = LoraConfig(
51
+ task_type=TaskType.CAUSAL_LM,
52
+ r=lora_rank,
53
+ lora_alpha=lora_alpha if lora_alpha is not None else lora_rank * 2,
54
+ lora_dropout=lora_dropout,
55
+ target_modules=lora_target,
56
+ init_lora_weights="pissa" if pissa_iter == -1 else f"pissa_niter_{pissa_iter}",
57
+ )
58
+
59
+ # Init PiSSA model
60
+ peft_model = get_peft_model(model, lora_config)
61
+ pissa_dir = os.path.join(output_dir, "pissa_init")
62
+
63
+ # Save PiSSA model
64
+ setattr(peft_model.peft_config["default"], "base_model_name_or_path", os.path.abspath(output_dir))
65
+ setattr(peft_model.peft_config["default"], "init_lora_weights", True) # don't apply pissa again
66
+ peft_model.save_pretrained(pissa_dir, safe_serialization=save_safetensors)
67
+ print(f"Adapter weights saved in {pissa_dir}")
68
+
69
+ # Save base model
70
+ base_model: PreTrainedModel = peft_model.unload()
71
+ base_model.save_pretrained(output_dir, safe_serialization=save_safetensors)
72
+ tokenizer.save_pretrained(output_dir)
73
+ print(f"Model weights saved in {output_dir}")
74
+
75
+ print("- Fine-tune this model with:")
76
+ print(f"model_name_or_path: {output_dir}")
77
+ print(f"adapter_name_or_path: {pissa_dir}")
78
+ print("finetuning_type: lora")
79
+ print("pissa_init: false")
80
+ print("pissa_convert: true")
81
+ print("- and optionally with:")
82
+ print("quantization_bit: 4")
83
+
84
+
85
+ if __name__ == "__main__":
86
+ fire.Fire(quantize_pissa)
scripts/qwen_omni_merge.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Why we need this script for qwen_omni?
16
+
17
+ Because the qwen_omni model is constructed by two parts:
18
+ 1. [Thinker]:[audio_encoder, vision_encoder, LLM backbone], which our repository does support to post-training.
19
+ 2. [Talker]: [audio_decoder, wave_model], which is not supported to post-training without specific tokenizer.
20
+ When we post-training the model, we exactly train the [Thinker] part, and the [Talker] part is dropped.
21
+ So, to get the complete model, we need to merge the [Talker] part back to the [Thinker] part.
22
+ LoRA mode: [Thinker + LoRA weights] + [Original Talker] -> [Omni model]
23
+ Full mode: [Thinker] + [Original Talker] -> [Omni model]
24
+ For Processor, we do saved the processor from trained model instead of the original model.
25
+ """
26
+
27
+ import os
28
+ import shutil
29
+
30
+ import fire
31
+ from peft import PeftModel
32
+ from transformers import AutoConfig, AutoModelForTextToWaveform, AutoProcessor
33
+ from transformers.utils import cached_file
34
+
35
+
36
+ def merge_lora(
37
+ model_path: str,
38
+ lora_path: str,
39
+ save_path: str = "./merged_model_checkpoint",
40
+ extra_file: str = "spk_dict.pt",
41
+ submodule_name: str = "thinker",
42
+ ):
43
+ """Load the original model, merge the LoRA weights.
44
+
45
+ For a specified submodule, and save the final merged model along with its configurations.
46
+
47
+ Args:
48
+ model_path (str): Path to the original model directory.
49
+ lora_path (str): Path to the directory containing LoRA weights.
50
+ save_path (str): Directory where the merged model and configurations will be saved.
51
+ extra_file (str): Name of the extra file to be copied (default: "spk_dict.pt").
52
+ submodule_name (str): Name of the submodule to merge (default: "thinker").
53
+ """
54
+ # 1. Load the original model
55
+ model = AutoModelForTextToWaveform.from_pretrained(model_path, torch_dtype="auto", device_map="cpu")
56
+ print("Successfully loaded the original model.")
57
+
58
+ # 2. Extract the submodule to be merged (e.g., model.thinker)
59
+ if not hasattr(model, submodule_name):
60
+ raise AttributeError(f"The model does not have a submodule named '{submodule_name}'.")
61
+
62
+ base_submodule = getattr(model, submodule_name)
63
+ print(f"Successfully extracted submodule: {submodule_name}.")
64
+
65
+ # 3. Load the LoRA weights onto the extracted submodule
66
+ lora_model = PeftModel.from_pretrained(base_submodule, lora_path)
67
+ processor = AutoProcessor.from_pretrained(lora_path)
68
+ print("Successfully loaded LoRA weights and processor.")
69
+
70
+ # 4. Merge the LoRA weights into the submodule and unload the LoRA modules
71
+ merged_submodule = lora_model.merge_and_unload()
72
+ print("Successfully merged LoRA weights.")
73
+
74
+ # 5. Replace the original submodule with the merged submodule in the model
75
+ setattr(model, submodule_name, merged_submodule)
76
+
77
+ # 6. Save the final merged model along with the tokenizer and processor configuration
78
+ model.save_pretrained(save_path)
79
+ processor.save_pretrained(save_path)
80
+ print(f"Merged model and processor saved to {save_path}.")
81
+
82
+ try:
83
+ source_file = cached_file(path_or_repo_id=model_path, filename=extra_file)
84
+ shutil.copy(source_file, os.path.join(save_path, extra_file))
85
+ print(f"File '{extra_file}' copied from {model_path} to {save_path}.")
86
+ except Exception:
87
+ print(f"File '{extra_file}' not found in {model_path}, skipping copy.")
88
+
89
+
90
+ def save_full_model(
91
+ model_path: str,
92
+ thinker_path: str,
93
+ save_path: str = "./merged_model_checkpoint",
94
+ extra_file: str = "spk_dict.pt",
95
+ ):
96
+ """Load the saved thinker module and the original model, replace the thinker in the original model.
97
+
98
+ Then save the complete model along with its tokenizer and processor configuration.
99
+
100
+ Args:
101
+ model_path (str): Directory path of the original model.
102
+ thinker_path (str): Path to the saved thinker weights.
103
+ save_path (str): Directory where the merged model and configurations will be saved.
104
+ extra_file (str): Name of the extra file to be copied (default: "spk_dict.pt").
105
+ """
106
+ # 1. Load the saved thinker module and the original model
107
+ config = AutoConfig.from_pretrained(model_path)
108
+ if getattr(config, "model_type") == "qwen2_5_omni":
109
+ from transformers.models.qwen2_5_omni import Qwen2_5OmniThinkerForConditionalGeneration # type: ignore
110
+
111
+ ThinkerClass = Qwen2_5OmniThinkerForConditionalGeneration
112
+ elif getattr(config, "model_type") == "qwen3_omni_moe":
113
+ from transformers.models.qwen3_omni_moe import Qwen3OmniMoeThinkerForConditionalGeneration # type: ignore
114
+
115
+ ThinkerClass = Qwen3OmniMoeThinkerForConditionalGeneration
116
+ else:
117
+ raise ValueError(f"Unsupported model type: {getattr(config, 'model_type')}.")
118
+
119
+ thinker = ThinkerClass.from_pretrained(thinker_path, torch_dtype="auto", device_map="cpu")
120
+ base_model = AutoModelForTextToWaveform.from_pretrained(model_path, torch_dtype="auto", device_map="cpu")
121
+ base_model.thinker = thinker
122
+ processor = AutoProcessor.from_pretrained(thinker_path)
123
+ print("Successfully loaded model weights and processor.")
124
+
125
+ # 2. Save the complete model along with its tokenizer and processor configuration
126
+ base_model.save_pretrained(save_path)
127
+ processor.save_pretrained(save_path)
128
+ print(f"Merged model and processor saved to {save_path}.")
129
+
130
+ # 3. Copy the extra file from the base model directory to the save_path
131
+ try:
132
+ source_file = cached_file(path_or_repo_id=model_path, filename=extra_file)
133
+ shutil.copy(source_file, os.path.join(save_path, extra_file))
134
+ print(f"File '{extra_file}' copied from {model_path} to {save_path}.")
135
+ except Exception:
136
+ print(f"File '{extra_file}' not found in {model_path}, skipping copy.")
137
+
138
+
139
+ if __name__ == "__main__":
140
+ fire.Fire({"save_full": save_full_model, "merge_lora": merge_lora})
scripts/stat_utils/cal_flops.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 Microsoft Corporation and the LlamaFactory team.
2
+ #
3
+ # This code is inspired by the Microsoft's DeepSpeed library.
4
+ # https://www.deepspeed.ai/tutorials/flops-profiler/
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ import fire
19
+ import torch
20
+ from deepspeed.accelerator import get_accelerator # type: ignore
21
+ from deepspeed.profiling.flops_profiler import get_model_profile # type: ignore
22
+
23
+ from llamafactory.chat import ChatModel
24
+
25
+
26
+ def calculate_flops(
27
+ model_name_or_path: str,
28
+ batch_size: int = 1,
29
+ seq_length: int = 512,
30
+ flash_attn: str = "auto",
31
+ ):
32
+ r"""Calculate the flops of pre-trained models.
33
+
34
+ Usage: python cal_flops.py --model_name_or_path path_to_model --batch_size 1 --seq_length 512
35
+ """
36
+ with get_accelerator().device(0):
37
+ chat_model = ChatModel(dict(model_name_or_path=model_name_or_path, template="empty", flash_attn=flash_attn))
38
+ fake_input = torch.ones((batch_size, seq_length), dtype=torch.long, device=chat_model.engine.model.device)
39
+ input_dict = {"input_ids": fake_input, "labels": fake_input.clone()}
40
+ flops, macs, params = get_model_profile(
41
+ chat_model.engine.model, kwargs=input_dict, print_profile=True, detailed=True
42
+ )
43
+ print("FLOPs:", flops)
44
+ print("MACs:", macs)
45
+ print("Params:", params)
46
+
47
+
48
+ if __name__ == "__main__":
49
+ fire.Fire(calculate_flops)
scripts/stat_utils/cal_lr.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 imoneoi and the LlamaFactory team.
2
+ #
3
+ # This code is inspired by the imoneoi's OpenChat library.
4
+ # https://github.com/imoneoi/openchat/blob/3.6.0/ochat/training_deepspeed/train.py
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ import math
19
+ from typing import Literal
20
+
21
+ import fire
22
+ import torch
23
+ from torch.utils.data import DataLoader
24
+ from tqdm import tqdm
25
+ from transformers import DataCollatorForLanguageModeling
26
+
27
+ from llamafactory.data import MultiModalDataCollatorForSeq2Seq, get_dataset, get_template_and_fix_tokenizer
28
+ from llamafactory.extras.constants import IGNORE_INDEX
29
+ from llamafactory.hparams import get_train_args
30
+ from llamafactory.model import load_tokenizer
31
+
32
+
33
+ BASE_LR = 3e-4 # 1.5e-4 for 30B-70B models
34
+ BASE_BS = 4_000_000 # from llama paper
35
+
36
+
37
+ def calculate_lr(
38
+ model_name_or_path: str,
39
+ batch_size: int, # total batch size, namely (batch size * gradient accumulation * world size)
40
+ stage: Literal["pt", "sft"] = "sft",
41
+ dataset: str = "alpaca_en_demo",
42
+ dataset_dir: str = "data",
43
+ template: str = "default",
44
+ cutoff_len: int = 2048, # i.e. maximum input length during training
45
+ is_mistral_or_gemma: bool = False, # mistral and gemma models opt for a smaller learning rate,
46
+ packing: bool = False,
47
+ ):
48
+ r"""Calculate the optimal learning rate for 7B/13B models using LLaMA's hyper-parameters.
49
+
50
+ Usage:
51
+ python cal_lr.py --model_name_or_path path_to_model --dataset alpaca_en_demo --cutoff_len 1024 --batch_size 16
52
+ """
53
+ model_args, data_args, training_args, _, _ = get_train_args(
54
+ dict(
55
+ stage=stage,
56
+ model_name_or_path=model_name_or_path,
57
+ dataset=dataset,
58
+ dataset_dir=dataset_dir,
59
+ template=template,
60
+ cutoff_len=cutoff_len,
61
+ packing=packing,
62
+ preprocessing_num_workers=16,
63
+ output_dir="dummy_dir",
64
+ overwrite_cache=True,
65
+ do_train=True,
66
+ )
67
+ )
68
+ tokenizer_module = load_tokenizer(model_args)
69
+ tokenizer = tokenizer_module["tokenizer"]
70
+ template = get_template_and_fix_tokenizer(tokenizer, data_args)
71
+ trainset = get_dataset(template, model_args, data_args, training_args, stage, **tokenizer_module)["train_dataset"]
72
+ if stage == "pt":
73
+ data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
74
+ elif stage == "sft":
75
+ data_collator = MultiModalDataCollatorForSeq2Seq(
76
+ template=template, tokenizer=tokenizer, label_pad_token_id=IGNORE_INDEX
77
+ )
78
+ else:
79
+ raise NotImplementedError(f"Stage does not supported: {stage}.")
80
+
81
+ dataloader = DataLoader(trainset, batch_size, shuffle=False, collate_fn=data_collator, pin_memory=True)
82
+ valid_tokens, total_tokens = 0, 0
83
+ for batch in tqdm(dataloader, desc="Collecting valid tokens"):
84
+ valid_tokens += torch.sum(batch["labels"] != IGNORE_INDEX).item()
85
+ total_tokens += torch.numel(batch["labels"])
86
+
87
+ valid_ratio = valid_tokens / total_tokens
88
+ token_batch_size = cutoff_len * batch_size * valid_ratio
89
+ lr = BASE_LR * math.sqrt(token_batch_size / BASE_BS) # lr ~ sqrt(batch_size)
90
+ lr = lr / 6.0 if is_mistral_or_gemma else lr
91
+ print(
92
+ f"Optimal learning rate is {lr:.2e} for valid ratio% {valid_ratio * 100:.2f} "
93
+ f"and effective token batch size {token_batch_size:.2f}"
94
+ )
95
+
96
+
97
+ if __name__ == "__main__":
98
+ fire.Fire(calculate_lr)
scripts/stat_utils/cal_mfu.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ import os
17
+
18
+ import fire
19
+ import torch
20
+ import torch.distributed as dist
21
+ from transformers import AutoConfig
22
+
23
+ from llamafactory.train.tuner import run_exp
24
+
25
+
26
+ BASE = 2 # gemm (add + mul)
27
+
28
+
29
+ def compute_model_flops(
30
+ model_name_or_path: str,
31
+ total_batch_size: int,
32
+ seq_length: int,
33
+ include_backward: bool = True,
34
+ include_recompute: bool = False,
35
+ include_flashattn: bool = False,
36
+ ) -> int:
37
+ r"""Calculate the FLOPs of model per forward/backward pass."""
38
+ config = AutoConfig.from_pretrained(model_name_or_path)
39
+ hidden_size = getattr(config, "hidden_size", None)
40
+ vocab_size = getattr(config, "vocab_size", None)
41
+ intermediate_size = getattr(config, "intermediate_size", None)
42
+ num_attention_heads = getattr(config, "num_attention_heads", None)
43
+ num_key_value_heads = getattr(config, "num_key_value_heads", None)
44
+ num_hidden_layers = getattr(config, "num_hidden_layers", None)
45
+ tie_word_embeddings = getattr(config, "tie_word_embeddings", False)
46
+
47
+ # mlp module
48
+ mlp_flops_per_token = 3 * BASE * hidden_size * intermediate_size # up, gate, down
49
+ mlp_flops = total_batch_size * seq_length * num_hidden_layers * mlp_flops_per_token
50
+
51
+ # attn projector module
52
+ q_flops_per_token = BASE * hidden_size * hidden_size
53
+ o_flops_per_token = BASE * hidden_size * hidden_size
54
+ k_flops_per_token = BASE * hidden_size * hidden_size * num_key_value_heads // num_attention_heads
55
+ v_flops_per_token = BASE * hidden_size * hidden_size * num_key_value_heads // num_attention_heads
56
+ attn_proj_flops_per_token = q_flops_per_token + o_flops_per_token + k_flops_per_token + v_flops_per_token
57
+ attn_proj_flops = total_batch_size * seq_length * num_hidden_layers * attn_proj_flops_per_token
58
+
59
+ # attn sdpa module
60
+ sdpa_flops_per_layer = 2 * BASE * hidden_size * seq_length * seq_length # (q * k^T) * v
61
+ sdpa_flops = total_batch_size * num_hidden_layers * sdpa_flops_per_layer
62
+
63
+ # embedding module
64
+ embedding_flops_per_token = hidden_size * vocab_size
65
+ embedding_flops = total_batch_size * seq_length * embedding_flops_per_token
66
+ if tie_word_embeddings is False:
67
+ embedding_flops *= 2
68
+
69
+ non_embedding_flops = mlp_flops + attn_proj_flops + sdpa_flops
70
+ non_embedding_coeff, embedding_coeff = 1, 1
71
+ if include_backward:
72
+ non_embedding_coeff += 2
73
+ embedding_coeff += 2
74
+
75
+ if include_recompute:
76
+ non_embedding_coeff += 1
77
+
78
+ total_flops = non_embedding_coeff * non_embedding_flops + embedding_coeff * embedding_flops
79
+
80
+ if include_flashattn:
81
+ total_flops += sdpa_flops
82
+
83
+ return total_flops
84
+
85
+
86
+ def compute_device_flops(world_size: int) -> float:
87
+ r"""Calculate the FLOPs of the device capability per second."""
88
+ device_name = torch.cuda.get_device_name()
89
+ if "H100" in device_name or "H800" in device_name:
90
+ return 989 * 1e12 * world_size
91
+ elif "A100" in device_name or "A800" in device_name:
92
+ return 312 * 1e12 * world_size
93
+ elif "V100" in device_name:
94
+ return 125 * 1e12 * world_size
95
+ elif "4090" in device_name:
96
+ return 98 * 1e12 * world_size
97
+ else:
98
+ raise NotImplementedError(f"Device not supported: {device_name}.")
99
+
100
+
101
+ def calculate_mfu(
102
+ model_name_or_path: str,
103
+ batch_size: int = 1,
104
+ seq_length: int = 1024,
105
+ num_steps: int = 100,
106
+ finetuning_type: str = "lora",
107
+ flash_attn: str = "auto",
108
+ deepspeed_stage: int = 0,
109
+ disable_gc: bool = False,
110
+ liger_kernel: bool = False,
111
+ unsloth_gc: bool = False,
112
+ ) -> float:
113
+ r"""Calculate MFU for given model and hyper-params.
114
+
115
+ Usage: python cal_mfu.py --model_name_or_path path_to_model --batch_size 1 --seq_length 1024
116
+ """
117
+ args = {
118
+ "model_name_or_path": model_name_or_path,
119
+ "flash_attn": flash_attn,
120
+ "disable_gradient_checkpointing": disable_gc,
121
+ "enable_liger_kernel": liger_kernel,
122
+ "use_unsloth_gc": unsloth_gc,
123
+ "stage": "pt",
124
+ "do_train": True,
125
+ "finetuning_type": finetuning_type,
126
+ "dataset": "c4_demo",
127
+ "cutoff_len": seq_length,
128
+ "output_dir": os.path.join("saves", "test_mfu"),
129
+ "logging_strategy": "no",
130
+ "save_strategy": "no",
131
+ "save_only_model": True,
132
+ "overwrite_output_dir": True,
133
+ "per_device_train_batch_size": batch_size,
134
+ "max_steps": num_steps,
135
+ "bf16": True,
136
+ }
137
+ if deepspeed_stage in [2, 3]:
138
+ args["deepspeed"] = f"examples/deepspeed/ds_z{deepspeed_stage}_config.json"
139
+
140
+ run_exp(args)
141
+ if dist.is_initialized():
142
+ dist.barrier()
143
+ world_size = dist.get_world_size()
144
+ else:
145
+ world_size = 1
146
+
147
+ if int(os.getenv("LOCAL_RANK", "0")) == 0:
148
+ with open(os.path.join("saves", "test_mfu", "all_results.json"), encoding="utf-8") as f:
149
+ result = json.load(f)
150
+
151
+ total_batch_size = batch_size * world_size
152
+ mfu_value = (
153
+ result["train_steps_per_second"]
154
+ * compute_model_flops(model_name_or_path, total_batch_size, seq_length)
155
+ / compute_device_flops(world_size)
156
+ )
157
+ print(f"MFU: {mfu_value * 100:.2f}%")
158
+
159
+
160
+ if __name__ == "__main__":
161
+ fire.Fire(calculate_mfu)
scripts/stat_utils/cal_ppl.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ from dataclasses import dataclass
17
+ from typing import Any, Literal
18
+
19
+ import fire
20
+ import torch
21
+ from torch.utils.data import DataLoader
22
+ from tqdm import tqdm
23
+ from transformers import DataCollatorForLanguageModeling
24
+
25
+ from llamafactory.data import MultiModalDataCollatorForSeq2Seq, get_dataset, get_template_and_fix_tokenizer
26
+ from llamafactory.extras.constants import IGNORE_INDEX
27
+ from llamafactory.hparams import get_train_args
28
+ from llamafactory.model import load_model, load_tokenizer
29
+
30
+
31
+ @dataclass
32
+ class PairwiseDataCollatorWithPadding(MultiModalDataCollatorForSeq2Seq):
33
+ r"""Data collator for pairwise data."""
34
+
35
+ train_on_prompt: bool = False
36
+
37
+ def __call__(self, features: list[dict[str, Any]]) -> dict[str, torch.Tensor]:
38
+ r"""Pad batched data to the longest sequence in the batch."""
39
+ chosen_features = []
40
+ for feature in features:
41
+ chosen_features.append(
42
+ {
43
+ "input_ids": feature["chosen_input_ids"],
44
+ "attention_mask": feature["chosen_attention_mask"],
45
+ "labels": feature["chosen_input_ids"] if self.train_on_prompt else feature["chosen_labels"],
46
+ "images": feature["images"],
47
+ "videos": feature["videos"],
48
+ "audios": feature["audios"],
49
+ }
50
+ )
51
+
52
+ return super().__call__(chosen_features)
53
+
54
+
55
+ def calculate_ppl(
56
+ model_name_or_path: str,
57
+ save_name: str = "ppl.json",
58
+ batch_size: int = 4,
59
+ stage: Literal["pt", "sft", "rm"] = "sft",
60
+ dataset: str = "alpaca_en_demo",
61
+ dataset_dir: str = "data",
62
+ template: str = "default",
63
+ cutoff_len: int = 2048,
64
+ max_samples: int | None = None,
65
+ train_on_prompt: bool = False,
66
+ ):
67
+ r"""Calculate the ppl on the dataset of the pre-trained models.
68
+
69
+ Usage: export CUDA_VISIBLE_DEVICES=0
70
+ python cal_ppl.py --model_name_or_path path_to_model --dataset alpaca_en_demo --save_name ppl.json
71
+ """
72
+ model_args, data_args, training_args, finetuning_args, _ = get_train_args(
73
+ dict(
74
+ stage=stage,
75
+ model_name_or_path=model_name_or_path,
76
+ dataset=dataset,
77
+ dataset_dir=dataset_dir,
78
+ template=template,
79
+ cutoff_len=cutoff_len,
80
+ max_samples=max_samples,
81
+ train_on_prompt=train_on_prompt,
82
+ preprocessing_num_workers=16,
83
+ output_dir="dummy_dir",
84
+ overwrite_cache=True,
85
+ do_train=True,
86
+ )
87
+ )
88
+ tokenizer_module = load_tokenizer(model_args)
89
+ tokenizer = tokenizer_module["tokenizer"]
90
+ template = get_template_and_fix_tokenizer(tokenizer, data_args)
91
+ trainset = get_dataset(template, model_args, data_args, training_args, stage, **tokenizer_module)["train_dataset"]
92
+ model = load_model(tokenizer, model_args, finetuning_args, is_trainable=False)
93
+ if stage == "pt":
94
+ data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
95
+ elif stage == "sft":
96
+ data_collator = MultiModalDataCollatorForSeq2Seq(
97
+ template=template, tokenizer=tokenizer, label_pad_token_id=IGNORE_INDEX
98
+ )
99
+ elif stage == "rm":
100
+ data_collator = PairwiseDataCollatorWithPadding(
101
+ template=template, tokenizer=tokenizer, label_pad_token_id=IGNORE_INDEX, train_on_prompt=train_on_prompt
102
+ )
103
+ else:
104
+ raise NotImplementedError(f"Stage does not supported: {stage}.")
105
+
106
+ dataloader = DataLoader(trainset, batch_size, shuffle=False, collate_fn=data_collator, pin_memory=True)
107
+ criterion = torch.nn.CrossEntropyLoss(reduction="none")
108
+ total_ppl = 0
109
+ perplexities = []
110
+ batch: dict[str, torch.Tensor]
111
+ with torch.no_grad():
112
+ for batch in tqdm(dataloader, desc="Computing perplexities"):
113
+ batch = batch.to(model.device)
114
+ outputs = model(**batch)
115
+ shift_logits: torch.Tensor = outputs["logits"][..., :-1, :]
116
+ shift_labels: torch.Tensor = batch["labels"][..., 1:]
117
+ loss_mask = shift_labels != IGNORE_INDEX
118
+ flatten_logits = shift_logits.contiguous().view(shift_labels.size(0) * shift_labels.size(1), -1)
119
+ flatten_labels = shift_labels.contiguous().view(-1)
120
+ token_logps: torch.Tensor = criterion(flatten_logits, flatten_labels)
121
+ token_logps = token_logps.contiguous().view(shift_logits.size(0), -1)
122
+ sentence_logps = (token_logps * loss_mask).sum(-1) / loss_mask.sum(-1)
123
+ total_ppl += sentence_logps.exp().sum().item()
124
+ perplexities.extend(sentence_logps.exp().tolist())
125
+
126
+ with open(save_name, "w", encoding="utf-8") as f:
127
+ json.dump(perplexities, f, indent=2)
128
+
129
+ print(f"Average perplexity is {total_ppl / len(perplexities):.2f}")
130
+ print(f"Perplexities have been saved at {save_name}.")
131
+
132
+
133
+ if __name__ == "__main__":
134
+ fire.Fire(calculate_ppl)
scripts/stat_utils/length_cdf.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from collections import defaultdict
16
+
17
+ import fire
18
+ from tqdm import tqdm
19
+
20
+ from llamafactory.data import get_dataset, get_template_and_fix_tokenizer
21
+ from llamafactory.hparams import get_train_args
22
+ from llamafactory.model import load_tokenizer
23
+
24
+
25
+ def length_cdf(
26
+ model_name_or_path: str,
27
+ dataset: str = "alpaca_en_demo",
28
+ dataset_dir: str = "data",
29
+ template: str = "default",
30
+ interval: int = 1000,
31
+ ):
32
+ r"""Calculate the distribution of the input lengths in the dataset.
33
+
34
+ Usage: export CUDA_VISIBLE_DEVICES=0
35
+ python length_cdf.py --model_name_or_path path_to_model --dataset alpaca_en_demo --template default
36
+ """
37
+ model_args, data_args, training_args, _, _ = get_train_args(
38
+ dict(
39
+ stage="sft",
40
+ model_name_or_path=model_name_or_path,
41
+ dataset=dataset,
42
+ dataset_dir=dataset_dir,
43
+ template=template,
44
+ cutoff_len=1_000_000,
45
+ preprocessing_num_workers=16,
46
+ output_dir="dummy_dir",
47
+ overwrite_cache=True,
48
+ do_train=True,
49
+ )
50
+ )
51
+ tokenizer_module = load_tokenizer(model_args)
52
+ template = get_template_and_fix_tokenizer(tokenizer_module["tokenizer"], data_args)
53
+ trainset = get_dataset(template, model_args, data_args, training_args, "sft", **tokenizer_module)["train_dataset"]
54
+ total_num = len(trainset)
55
+ length_dict = defaultdict(int)
56
+ for sample in tqdm(trainset["input_ids"], desc="Collecting lengths"):
57
+ length_dict[len(sample) // interval * interval] += 1
58
+
59
+ length_tuples = list(length_dict.items())
60
+ length_tuples.sort()
61
+ count_accu, prob_accu = 0, 0
62
+ for length, count in length_tuples:
63
+ count_accu += count
64
+ prob_accu += count / total_num * 100
65
+ print(f"{count_accu:d} ({prob_accu:.2f}%) samples have length < {length + interval}.")
66
+
67
+
68
+ if __name__ == "__main__":
69
+ fire.Fire(length_cdf)
scripts/vllm_infer.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import gc
16
+ import json
17
+ import time
18
+
19
+ import av
20
+ import fire
21
+ from datasets import load_dataset
22
+ from eval_bleu_rouge import compute_metrics
23
+ from tqdm import tqdm
24
+ from transformers import Seq2SeqTrainingArguments
25
+
26
+ from llamafactory.data import get_dataset, get_template_and_fix_tokenizer
27
+ from llamafactory.extras.constants import IGNORE_INDEX
28
+ from llamafactory.extras.misc import get_device_count
29
+ from llamafactory.extras.packages import is_vllm_available
30
+ from llamafactory.hparams import get_infer_args
31
+ from llamafactory.model import load_tokenizer
32
+
33
+
34
+ if is_vllm_available():
35
+ from vllm import LLM, SamplingParams
36
+ from vllm.lora.request import LoRARequest
37
+
38
+
39
+ def _need_video_kwargs(template):
40
+ NEEDED_TEMPLATE = ["qwen3_vl", "glm4v"]
41
+ if any(t in template for t in NEEDED_TEMPLATE):
42
+ return True
43
+
44
+ return False
45
+
46
+
47
+ def vllm_infer(
48
+ model_name_or_path: str,
49
+ adapter_name_or_path: str = None,
50
+ dataset: str = "alpaca_en_demo",
51
+ dataset_dir: str = "data",
52
+ template: str = "default",
53
+ cutoff_len: int = 2048,
54
+ max_samples: int | None = None,
55
+ vllm_config: str = "{}",
56
+ save_name: str = "generated_predictions.jsonl",
57
+ matrix_save_name: str = None,
58
+ temperature: float = 0.95,
59
+ top_p: float = 0.7,
60
+ top_k: int = 50,
61
+ max_new_tokens: int = 1024,
62
+ repetition_penalty: float = 1.0,
63
+ skip_special_tokens: bool = True,
64
+ default_system: str | None = None,
65
+ enable_thinking: bool = True,
66
+ seed: int | None = None,
67
+ pipeline_parallel_size: int = 1,
68
+ image_max_pixels: int = 768 * 768,
69
+ image_min_pixels: int = 32 * 32,
70
+ video_fps: float = 2.0,
71
+ video_maxlen: int = 128,
72
+ batch_size: int = 1024,
73
+ ):
74
+ r"""Perform batch generation using vLLM engine, which supports tensor parallelism.
75
+
76
+ Usage: python vllm_infer.py --model_name_or_path meta-llama/Llama-2-7b-hf --template llama --dataset alpaca_en_demo
77
+ """
78
+ if pipeline_parallel_size > get_device_count():
79
+ raise ValueError("Pipeline parallel size should be smaller than the number of gpus.")
80
+
81
+ model_args, data_args, _, generating_args = get_infer_args(
82
+ dict(
83
+ model_name_or_path=model_name_or_path,
84
+ adapter_name_or_path=adapter_name_or_path,
85
+ dataset=dataset,
86
+ dataset_dir=dataset_dir,
87
+ template=template,
88
+ cutoff_len=cutoff_len,
89
+ max_samples=max_samples,
90
+ preprocessing_num_workers=16,
91
+ default_system=default_system,
92
+ enable_thinking=enable_thinking,
93
+ vllm_config=vllm_config,
94
+ temperature=temperature,
95
+ top_p=top_p,
96
+ top_k=top_k,
97
+ max_new_tokens=max_new_tokens,
98
+ repetition_penalty=repetition_penalty,
99
+ )
100
+ )
101
+
102
+ training_args = Seq2SeqTrainingArguments(output_dir="dummy_dir")
103
+ tokenizer_module = load_tokenizer(model_args)
104
+ tokenizer = tokenizer_module["tokenizer"]
105
+ template_obj = get_template_and_fix_tokenizer(tokenizer, data_args)
106
+ template_obj.mm_plugin.expand_mm_tokens = False # for vllm generate
107
+
108
+ engine_args = {
109
+ "model": model_args.model_name_or_path,
110
+ "trust_remote_code": True,
111
+ "dtype": model_args.infer_dtype,
112
+ "max_model_len": cutoff_len + max_new_tokens,
113
+ "tensor_parallel_size": (get_device_count() // pipeline_parallel_size) or 1,
114
+ "pipeline_parallel_size": pipeline_parallel_size,
115
+ "disable_log_stats": True,
116
+ "enable_lora": model_args.adapter_name_or_path is not None,
117
+ }
118
+ if template_obj.mm_plugin.__class__.__name__ != "BasePlugin":
119
+ engine_args["limit_mm_per_prompt"] = {"image": 4, "video": 2, "audio": 2}
120
+
121
+ if isinstance(model_args.vllm_config, dict):
122
+ engine_args.update(model_args.vllm_config)
123
+
124
+ model_preparation_start_time = time.time()
125
+ llm = LLM(**engine_args)
126
+
127
+ # load datasets
128
+ dataset_module = get_dataset(template_obj, model_args, data_args, training_args, "ppo", **tokenizer_module)
129
+ train_dataset = dataset_module["train_dataset"]
130
+
131
+ sampling_params = SamplingParams(
132
+ repetition_penalty=generating_args.repetition_penalty or 1.0, # repetition_penalty must > 0
133
+ temperature=generating_args.temperature,
134
+ top_p=generating_args.top_p or 1.0, # top_p must > 0
135
+ top_k=generating_args.top_k or -1, # top_k must > 0
136
+ stop_token_ids=template_obj.get_stop_token_ids(tokenizer),
137
+ max_tokens=generating_args.max_new_tokens,
138
+ skip_special_tokens=skip_special_tokens,
139
+ seed=seed,
140
+ )
141
+ if model_args.adapter_name_or_path is not None:
142
+ lora_request = LoRARequest("default", 1, model_args.adapter_name_or_path[0])
143
+ else:
144
+ lora_request = None
145
+
146
+ # Store all results in these lists
147
+ all_prompts, all_preds, all_labels = [], [], []
148
+ need_video_kwargs = _need_video_kwargs(template)
149
+
150
+ model_predict_start_time = time.time()
151
+ # Add batch process to avoid the issue of too many files opened
152
+ for i in tqdm(range(0, len(train_dataset), batch_size), desc="Processing batched inference"):
153
+ vllm_inputs, prompts, labels = [], [], []
154
+ batch = train_dataset[i : min(i + batch_size, len(train_dataset))]
155
+
156
+ for j in range(len(batch["input_ids"])):
157
+ multi_modal_data = {}
158
+ video_metadata_kwargs = None
159
+
160
+ if batch["images"][j] is not None:
161
+ image = batch["images"][j]
162
+ multi_modal_data["image"] = template_obj.mm_plugin._regularize_images(
163
+ image, image_max_pixels=image_max_pixels, image_min_pixels=image_min_pixels
164
+ )["images"]
165
+
166
+ if batch["videos"][j] is not None:
167
+ video = batch["videos"][j]
168
+ multi_modal_data["video"] = template_obj.mm_plugin._regularize_videos(
169
+ video,
170
+ image_max_pixels=image_max_pixels,
171
+ image_min_pixels=image_min_pixels,
172
+ video_fps=video_fps,
173
+ video_maxlen=video_maxlen,
174
+ )["videos"]
175
+ if need_video_kwargs:
176
+ container = av.open(video[0], "r")
177
+ video_stream = next(stream for stream in container.streams if stream.type == "video")
178
+ sampling_indices = template_obj.mm_plugin._get_video_sample_indices(
179
+ video_stream, video_fps, video_maxlen
180
+ )
181
+ total_frames = video_stream.frames
182
+ video_metadata_kwargs = {
183
+ "fps": getattr(tokenizer_module["processor"], "video_fps", 24.0),
184
+ "do_sample_frames": False,
185
+ "total_num_frames": total_frames,
186
+ }
187
+ video_metadata = dict(
188
+ fps=video_fps,
189
+ frames_indices=sampling_indices,
190
+ total_num_frames=total_frames,
191
+ video_backend="opencv",
192
+ )
193
+ multi_modal_data["video"] = (multi_modal_data["video"], video_metadata)
194
+
195
+ if batch["audios"][j] is not None:
196
+ audio = batch["audios"][j]
197
+ audio_data = template_obj.mm_plugin._regularize_audios(
198
+ audio,
199
+ sampling_rate=16000,
200
+ )
201
+ multi_modal_data["audio"] = zip(audio_data["audios"], audio_data["sampling_rates"])
202
+
203
+ vllm_input_data = {"prompt_token_ids": batch["input_ids"][j], "multi_modal_data": multi_modal_data or None}
204
+ if video_metadata_kwargs is not None:
205
+ vllm_input_data["mm_processor_kwargs"] = video_metadata_kwargs
206
+
207
+ vllm_inputs.append(vllm_input_data)
208
+ prompts.append(tokenizer.decode(batch["input_ids"][j], skip_special_tokens=skip_special_tokens))
209
+ labels.append(
210
+ tokenizer.decode(
211
+ list(filter(lambda x: x != IGNORE_INDEX, batch["labels"][j])),
212
+ skip_special_tokens=skip_special_tokens,
213
+ )
214
+ )
215
+
216
+ results = llm.generate(vllm_inputs, sampling_params, lora_request=lora_request)
217
+ preds = [result.outputs[0].text for result in results]
218
+
219
+ # Accumulate results
220
+ all_prompts.extend(prompts)
221
+ all_preds.extend(preds)
222
+ all_labels.extend(labels)
223
+ gc.collect()
224
+
225
+ model_predict_end_time = time.time()
226
+ # Write all results at once outside the loop
227
+ with open(save_name, "w", encoding="utf-8") as f:
228
+ for text, pred, label in zip(all_prompts, all_preds, all_labels):
229
+ f.write(json.dumps({"prompt": text, "predict": pred, "label": label}, ensure_ascii=False) + "\n")
230
+
231
+ print("*" * 70)
232
+ print(f"{len(all_prompts)} total generated results have been saved at {save_name}.")
233
+ print("*" * 70)
234
+
235
+ # Write all matrix results when matrix_save_name is not None,
236
+ # The result matrix is referencing src.llamafactory.train.sft.workflow.run_sft # 127~132
237
+ # trainer.save_metrics("predict", predict_results.metrics)
238
+ #
239
+ # {
240
+ # "predict_bleu-4": 4.349975,
241
+ # "predict_model_preparation_time": 0.0128,
242
+ # "predict_rouge-1": 21.873359375,
243
+ # "predict_rouge-2": 4.144340625,
244
+ # "predict_rouge-l": 10.83949375,
245
+ # "predict_runtime": 131.664,
246
+ # "predict_samples_per_second": 0.076,
247
+ # "predict_steps_per_second": 0.008
248
+ # }
249
+ #
250
+ if matrix_save_name is not None:
251
+ predict_time = model_predict_end_time - model_predict_start_time
252
+ preparation_time = model_predict_start_time - model_preparation_start_time
253
+
254
+ start_time = time.time()
255
+ dataset = load_dataset("json", data_files=save_name, split="train")
256
+ dataset = dataset.map(compute_metrics, num_proc=8, remove_columns=dataset.column_names)
257
+ score_dict = dataset.to_dict()
258
+
259
+ average_score = {}
260
+ for task, scores in sorted(score_dict.items(), key=lambda x: x[0]):
261
+ score = sum(scores) / len(scores) if scores else 0.0
262
+ print(f"predict_{task}: {score:.4f}")
263
+ average_score["predict_" + task] = score
264
+
265
+ average_score["predict_model_preparation_time"] = preparation_time
266
+ average_score["predict_runtime"] = predict_time
267
+ num_steps = len(range(0, len(train_dataset), batch_size))
268
+ average_score["predict_samples_per_second"] = len(dataset) / predict_time if predict_time > 0 else 0.0
269
+ average_score["predict_steps_per_second"] = num_steps / predict_time if predict_time > 0 else 0.0
270
+
271
+ with open(matrix_save_name, "w", encoding="utf-8") as f:
272
+ json.dump(average_score, f, indent=4)
273
+
274
+ print("*" * 70)
275
+ print(f"\nDone in {time.time() - start_time:.3f}s.\nScore file saved to {matrix_save_name}.")
276
+ print("*" * 70)
277
+
278
+
279
+ if __name__ == "__main__":
280
+ fire.Fire(vllm_infer)
src/api.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import os
16
+
17
+ import uvicorn
18
+
19
+ from llamafactory.api.app import create_app
20
+ from llamafactory.chat import ChatModel
21
+
22
+
23
+ def main():
24
+ chat_model = ChatModel()
25
+ app = create_app(chat_model)
26
+ api_host = os.getenv("API_HOST", "0.0.0.0")
27
+ api_port = int(os.getenv("API_PORT", "8000"))
28
+ print(f"Visit http://localhost:{api_port}/docs for API document.")
29
+ uvicorn.run(app, host=api_host, port=api_port)
30
+
31
+
32
+ if __name__ == "__main__":
33
+ main()
src/llamafactory/__init__.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ r"""Efficient fine-tuning of large language models.
16
+
17
+ Level:
18
+ api, webui > chat, eval, train > data, model > hparams > extras
19
+
20
+ Disable version checking: DISABLE_VERSION_CHECK=1
21
+ Enable VRAM recording: RECORD_VRAM=1
22
+ Force using torchrun: FORCE_TORCHRUN=1
23
+ Set logging verbosity: LLAMAFACTORY_VERBOSITY=WARN
24
+ Use modelscope: USE_MODELSCOPE_HUB=1
25
+ Use openmind: USE_OPENMIND_HUB=1
26
+ """
27
+
28
+ from .extras.env import VERSION
29
+
30
+
31
+ __version__ = VERSION
src/llamafactory/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (609 Bytes). View file
 
src/llamafactory/__pycache__/cli.cpython-312.pyc ADDED
Binary file (578 Bytes). View file
 
src/llamafactory/__pycache__/launcher.cpython-312.pyc ADDED
Binary file (6.29 kB). View file
 
src/llamafactory/api/__init__.py ADDED
File without changes
src/llamafactory/api/app.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import asyncio
16
+ import os
17
+ from contextlib import asynccontextmanager
18
+ from functools import partial
19
+ from typing import Annotated
20
+
21
+ from ..chat import ChatModel
22
+ from ..extras.constants import EngineName
23
+ from ..extras.misc import torch_gc
24
+ from ..extras.packages import is_fastapi_available, is_starlette_available, is_uvicorn_available
25
+ from .chat import (
26
+ create_chat_completion_response,
27
+ create_score_evaluation_response,
28
+ create_stream_chat_completion_response,
29
+ )
30
+ from .protocol import (
31
+ ChatCompletionRequest,
32
+ ChatCompletionResponse,
33
+ ModelCard,
34
+ ModelList,
35
+ ScoreEvaluationRequest,
36
+ ScoreEvaluationResponse,
37
+ )
38
+
39
+
40
+ if is_fastapi_available():
41
+ from fastapi import Depends, FastAPI, HTTPException, status
42
+ from fastapi.middleware.cors import CORSMiddleware
43
+ from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBearer
44
+
45
+
46
+ if is_starlette_available():
47
+ from sse_starlette import EventSourceResponse
48
+
49
+
50
+ if is_uvicorn_available():
51
+ import uvicorn
52
+
53
+
54
+ async def sweeper() -> None:
55
+ while True:
56
+ torch_gc()
57
+ await asyncio.sleep(300)
58
+
59
+
60
+ @asynccontextmanager
61
+ async def lifespan(app: "FastAPI", chat_model: "ChatModel"): # collects GPU memory
62
+ if chat_model.engine.name == EngineName.HF:
63
+ asyncio.create_task(sweeper())
64
+
65
+ yield
66
+ torch_gc()
67
+
68
+
69
+ def create_app(chat_model: "ChatModel") -> "FastAPI":
70
+ root_path = os.getenv("FASTAPI_ROOT_PATH", "")
71
+ app = FastAPI(lifespan=partial(lifespan, chat_model=chat_model), root_path=root_path)
72
+ app.add_middleware(
73
+ CORSMiddleware,
74
+ allow_origins=["*"],
75
+ allow_credentials=True,
76
+ allow_methods=["*"],
77
+ allow_headers=["*"],
78
+ )
79
+ api_key = os.getenv("API_KEY")
80
+ security = HTTPBearer(auto_error=False)
81
+
82
+ async def verify_api_key(auth: Annotated[HTTPAuthorizationCredentials | None, Depends(security)]):
83
+ if api_key and (auth is None or auth.credentials != api_key):
84
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key.")
85
+
86
+ @app.get(
87
+ "/v1/models",
88
+ response_model=ModelList,
89
+ status_code=status.HTTP_200_OK,
90
+ dependencies=[Depends(verify_api_key)],
91
+ )
92
+ async def list_models():
93
+ model_card = ModelCard(id=os.getenv("API_MODEL_NAME", "gpt-3.5-turbo"))
94
+ return ModelList(data=[model_card])
95
+
96
+ @app.post(
97
+ "/v1/chat/completions",
98
+ response_model=ChatCompletionResponse,
99
+ status_code=status.HTTP_200_OK,
100
+ dependencies=[Depends(verify_api_key)],
101
+ )
102
+ async def create_chat_completion(request: ChatCompletionRequest):
103
+ if not chat_model.engine.can_generate:
104
+ raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Not allowed")
105
+
106
+ if request.stream:
107
+ generate = create_stream_chat_completion_response(request, chat_model)
108
+ return EventSourceResponse(generate, media_type="text/event-stream", sep="\n")
109
+ else:
110
+ return await create_chat_completion_response(request, chat_model)
111
+
112
+ @app.post(
113
+ "/v1/score/evaluation",
114
+ response_model=ScoreEvaluationResponse,
115
+ status_code=status.HTTP_200_OK,
116
+ dependencies=[Depends(verify_api_key)],
117
+ )
118
+ async def create_score_evaluation(request: ScoreEvaluationRequest):
119
+ if chat_model.engine.can_generate:
120
+ raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Not allowed")
121
+
122
+ return await create_score_evaluation_response(request, chat_model)
123
+
124
+ return app
125
+
126
+
127
+ def run_api() -> None:
128
+ chat_model = ChatModel()
129
+ app = create_app(chat_model)
130
+ api_host = os.getenv("API_HOST", "0.0.0.0")
131
+ api_port = int(os.getenv("API_PORT", "8000"))
132
+ print(f"Visit http://localhost:{api_port}/docs for API document.")
133
+ uvicorn.run(app, host=api_host, port=api_port)
src/llamafactory/api/chat.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import base64
16
+ import io
17
+ import json
18
+ import os
19
+ import re
20
+ import uuid
21
+ from collections.abc import AsyncGenerator
22
+ from typing import TYPE_CHECKING, Optional
23
+
24
+ from ..data import Role as DataRole
25
+ from ..extras import logging
26
+ from ..extras.constants import AUDIO_PLACEHOLDER, IMAGE_PLACEHOLDER, VIDEO_PLACEHOLDER
27
+ from ..extras.misc import is_env_enabled
28
+ from ..extras.packages import is_fastapi_available, is_pillow_available, is_requests_available
29
+ from .common import check_lfi_path, check_ssrf_url, dictify, jsonify
30
+ from .protocol import (
31
+ ChatCompletionMessage,
32
+ ChatCompletionResponse,
33
+ ChatCompletionResponseChoice,
34
+ ChatCompletionResponseUsage,
35
+ ChatCompletionStreamResponse,
36
+ ChatCompletionStreamResponseChoice,
37
+ Finish,
38
+ Function,
39
+ FunctionCall,
40
+ Role,
41
+ ScoreEvaluationResponse,
42
+ )
43
+
44
+
45
+ if is_fastapi_available():
46
+ from fastapi import HTTPException, status
47
+
48
+
49
+ if is_pillow_available():
50
+ from PIL import Image
51
+
52
+
53
+ if is_requests_available():
54
+ import requests
55
+
56
+
57
+ if TYPE_CHECKING:
58
+ from ..chat import ChatModel
59
+ from ..data.mm_plugin import AudioInput, ImageInput, VideoInput
60
+ from .protocol import ChatCompletionRequest, ScoreEvaluationRequest
61
+
62
+
63
+ logger = logging.get_logger(__name__)
64
+ ROLE_MAPPING = {
65
+ Role.USER: DataRole.USER.value,
66
+ Role.ASSISTANT: DataRole.ASSISTANT.value,
67
+ Role.SYSTEM: DataRole.SYSTEM.value,
68
+ Role.FUNCTION: DataRole.FUNCTION.value,
69
+ Role.TOOL: DataRole.OBSERVATION.value,
70
+ }
71
+
72
+
73
+ def _process_request(
74
+ request: "ChatCompletionRequest",
75
+ ) -> tuple[
76
+ list[dict[str, str]],
77
+ Optional[str],
78
+ Optional[str],
79
+ Optional[list["ImageInput"]],
80
+ Optional[list["VideoInput"]],
81
+ Optional[list["AudioInput"]],
82
+ ]:
83
+ if is_env_enabled("API_VERBOSE", "1"):
84
+ logger.info_rank0(f"==== request ====\n{json.dumps(dictify(request), indent=2, ensure_ascii=False)}")
85
+
86
+ if len(request.messages) == 0:
87
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid length")
88
+
89
+ if request.messages[0].role == Role.SYSTEM:
90
+ content = request.messages.pop(0).content
91
+ if isinstance(content, list):
92
+ system = content[0].text if content else ""
93
+ else:
94
+ system = content
95
+ else:
96
+ system = None
97
+
98
+ if len(request.messages) % 2 == 0:
99
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only supports u/a/u/a/u...")
100
+
101
+ input_messages = []
102
+ images, videos, audios = [], [], []
103
+ for i, message in enumerate(request.messages):
104
+ if i % 2 == 0 and message.role not in [Role.USER, Role.TOOL]:
105
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid role")
106
+ elif i % 2 == 1 and message.role not in [Role.ASSISTANT, Role.FUNCTION]:
107
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid role")
108
+
109
+ if message.role == Role.ASSISTANT and isinstance(message.tool_calls, list) and len(message.tool_calls):
110
+ tool_calls = [
111
+ {"name": tool_call.function.name, "arguments": tool_call.function.arguments}
112
+ for tool_call in message.tool_calls
113
+ ]
114
+ content = json.dumps(tool_calls, ensure_ascii=False)
115
+ input_messages.append({"role": ROLE_MAPPING[Role.FUNCTION], "content": content})
116
+ elif isinstance(message.content, list):
117
+ text_content = ""
118
+ for input_item in message.content:
119
+ if input_item.type == "text":
120
+ text_content += input_item.text
121
+ elif input_item.type == "image_url":
122
+ text_content += IMAGE_PLACEHOLDER
123
+ image_url = input_item.image_url.url
124
+ if re.match(r"^data:image\/(png|jpg|jpeg|gif|bmp);base64,(.+)$", image_url): # base64 image
125
+ image_stream = io.BytesIO(base64.b64decode(image_url.split(",", maxsplit=1)[1]))
126
+ elif os.path.isfile(image_url): # local file
127
+ check_lfi_path(image_url)
128
+ image_stream = open(image_url, "rb")
129
+ else: # web uri
130
+ check_ssrf_url(image_url)
131
+ image_stream = requests.get(image_url, stream=True).raw
132
+
133
+ images.append(Image.open(image_stream).convert("RGB"))
134
+ elif input_item.type == "video_url":
135
+ text_content += VIDEO_PLACEHOLDER
136
+ video_url = input_item.video_url.url
137
+ if re.match(r"^data:video\/(mp4|mkv|avi|mov);base64,(.+)$", video_url): # base64 video
138
+ video_stream = io.BytesIO(base64.b64decode(video_url.split(",", maxsplit=1)[1]))
139
+ elif os.path.isfile(video_url): # local file
140
+ check_lfi_path(video_url)
141
+ video_stream = video_url
142
+ else: # web uri
143
+ check_ssrf_url(video_url)
144
+ video_stream = requests.get(video_url, stream=True).raw
145
+
146
+ videos.append(video_stream)
147
+ elif input_item.type == "audio_url":
148
+ text_content += AUDIO_PLACEHOLDER
149
+ audio_url = input_item.audio_url.url
150
+ if re.match(r"^data:audio\/(mpeg|mp3|wav|ogg);base64,(.+)$", audio_url): # base64 audio
151
+ audio_stream = io.BytesIO(base64.b64decode(audio_url.split(",", maxsplit=1)[1]))
152
+ elif os.path.isfile(audio_url): # local file
153
+ check_lfi_path(audio_url)
154
+ audio_stream = audio_url
155
+ else: # web uri
156
+ check_ssrf_url(audio_url)
157
+ audio_stream = requests.get(audio_url, stream=True).raw
158
+
159
+ audios.append(audio_stream)
160
+ else:
161
+ raise HTTPException(
162
+ status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid input type {input_item.type}."
163
+ )
164
+
165
+ input_messages.append({"role": ROLE_MAPPING[message.role], "content": text_content})
166
+ else:
167
+ input_messages.append({"role": ROLE_MAPPING[message.role], "content": message.content})
168
+
169
+ tool_list = request.tools
170
+ if isinstance(tool_list, list) and len(tool_list):
171
+ try:
172
+ tools = json.dumps([dictify(tool.function) for tool in tool_list], ensure_ascii=False)
173
+ except json.JSONDecodeError:
174
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid tools")
175
+ else:
176
+ tools = None
177
+
178
+ return input_messages, system, tools, images or None, videos or None, audios or None
179
+
180
+
181
+ def _create_stream_chat_completion_chunk(
182
+ completion_id: str,
183
+ model: str,
184
+ delta: "ChatCompletionMessage",
185
+ index: Optional[int] = 0,
186
+ finish_reason: Optional["Finish"] = None,
187
+ ) -> str:
188
+ choice_data = ChatCompletionStreamResponseChoice(index=index, delta=delta, finish_reason=finish_reason)
189
+ chunk = ChatCompletionStreamResponse(id=completion_id, model=model, choices=[choice_data])
190
+ return jsonify(chunk)
191
+
192
+
193
+ async def create_chat_completion_response(
194
+ request: "ChatCompletionRequest", chat_model: "ChatModel"
195
+ ) -> "ChatCompletionResponse":
196
+ completion_id = f"chatcmpl-{uuid.uuid4().hex}"
197
+ input_messages, system, tools, images, videos, audios = _process_request(request)
198
+ responses = await chat_model.achat(
199
+ input_messages,
200
+ system,
201
+ tools,
202
+ images,
203
+ videos,
204
+ audios,
205
+ do_sample=request.do_sample,
206
+ temperature=request.temperature,
207
+ top_p=request.top_p,
208
+ max_new_tokens=request.max_tokens,
209
+ num_return_sequences=request.n,
210
+ repetition_penalty=request.presence_penalty,
211
+ stop=request.stop,
212
+ )
213
+
214
+ prompt_length, response_length = 0, 0
215
+ choices = []
216
+ for i, response in enumerate(responses):
217
+ if tools:
218
+ result = chat_model.engine.template.extract_tool(response.response_text)
219
+ else:
220
+ result = response.response_text
221
+
222
+ if isinstance(result, list):
223
+ tool_calls = []
224
+ for tool in result:
225
+ function = Function(name=tool.name, arguments=tool.arguments)
226
+ tool_calls.append(FunctionCall(id=f"call_{uuid.uuid4().hex}", function=function))
227
+
228
+ response_message = ChatCompletionMessage(role=Role.ASSISTANT, tool_calls=tool_calls)
229
+ finish_reason = Finish.TOOL
230
+ else:
231
+ response_message = ChatCompletionMessage(role=Role.ASSISTANT, content=result)
232
+ finish_reason = Finish.STOP if response.finish_reason == "stop" else Finish.LENGTH
233
+
234
+ choices.append(ChatCompletionResponseChoice(index=i, message=response_message, finish_reason=finish_reason))
235
+ prompt_length = response.prompt_length
236
+ response_length += response.response_length
237
+
238
+ usage = ChatCompletionResponseUsage(
239
+ prompt_tokens=prompt_length,
240
+ completion_tokens=response_length,
241
+ total_tokens=prompt_length + response_length,
242
+ )
243
+
244
+ return ChatCompletionResponse(id=completion_id, model=request.model, choices=choices, usage=usage)
245
+
246
+
247
+ async def create_stream_chat_completion_response(
248
+ request: "ChatCompletionRequest", chat_model: "ChatModel"
249
+ ) -> AsyncGenerator[str, None]:
250
+ completion_id = f"chatcmpl-{uuid.uuid4().hex}"
251
+ input_messages, system, tools, images, videos, audios = _process_request(request)
252
+ if tools:
253
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot stream function calls.")
254
+
255
+ if request.n > 1:
256
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot stream multiple responses.")
257
+
258
+ yield _create_stream_chat_completion_chunk(
259
+ completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(role=Role.ASSISTANT, content="")
260
+ )
261
+ async for new_token in chat_model.astream_chat(
262
+ input_messages,
263
+ system,
264
+ tools,
265
+ images,
266
+ videos,
267
+ audios,
268
+ do_sample=request.do_sample,
269
+ temperature=request.temperature,
270
+ top_p=request.top_p,
271
+ max_new_tokens=request.max_tokens,
272
+ repetition_penalty=request.presence_penalty,
273
+ stop=request.stop,
274
+ ):
275
+ if len(new_token) != 0:
276
+ yield _create_stream_chat_completion_chunk(
277
+ completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(content=new_token)
278
+ )
279
+
280
+ yield _create_stream_chat_completion_chunk(
281
+ completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(), finish_reason=Finish.STOP
282
+ )
283
+ yield "[DONE]"
284
+
285
+
286
+ async def create_score_evaluation_response(
287
+ request: "ScoreEvaluationRequest", chat_model: "ChatModel"
288
+ ) -> "ScoreEvaluationResponse":
289
+ score_id = f"scoreval-{uuid.uuid4().hex}"
290
+ if len(request.messages) == 0:
291
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid request")
292
+
293
+ scores = await chat_model.aget_scores(request.messages, max_length=request.max_length)
294
+ return ScoreEvaluationResponse(id=score_id, model=request.model, scores=scores)
src/llamafactory/api/common.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import ipaddress
16
+ import json
17
+ import os
18
+ import socket
19
+ from typing import TYPE_CHECKING, Any
20
+ from urllib.parse import urlparse
21
+
22
+ from ..extras.misc import is_env_enabled
23
+ from ..extras.packages import is_fastapi_available
24
+
25
+
26
+ if is_fastapi_available():
27
+ from fastapi import HTTPException, status
28
+
29
+
30
+ if TYPE_CHECKING:
31
+ from pydantic import BaseModel
32
+
33
+
34
+ SAFE_MEDIA_PATH = os.environ.get("SAFE_MEDIA_PATH", os.path.join(os.path.dirname(__file__), "safe_media"))
35
+ ALLOW_LOCAL_FILES = is_env_enabled("ALLOW_LOCAL_FILES", "1")
36
+
37
+
38
+ def dictify(data: "BaseModel") -> dict[str, Any]:
39
+ try: # pydantic v2
40
+ return data.model_dump(exclude_unset=True)
41
+ except AttributeError: # pydantic v1
42
+ return data.dict(exclude_unset=True)
43
+
44
+
45
+ def jsonify(data: "BaseModel") -> str:
46
+ try: # pydantic v2
47
+ return json.dumps(data.model_dump(exclude_unset=True), ensure_ascii=False)
48
+ except AttributeError: # pydantic v1
49
+ return data.json(exclude_unset=True, ensure_ascii=False)
50
+
51
+
52
+ def check_lfi_path(path: str) -> None:
53
+ """Checks if a given path is vulnerable to LFI. Raises HTTPException if unsafe."""
54
+ if not ALLOW_LOCAL_FILES:
55
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Local file access is disabled.")
56
+
57
+ try:
58
+ os.makedirs(SAFE_MEDIA_PATH, exist_ok=True)
59
+ real_path = os.path.realpath(path)
60
+ safe_path = os.path.realpath(SAFE_MEDIA_PATH)
61
+
62
+ if not real_path.startswith(safe_path):
63
+ raise HTTPException(
64
+ status_code=status.HTTP_403_FORBIDDEN, detail="File access is restricted to the safe media directory."
65
+ )
66
+ except Exception:
67
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid or inaccessible file path.")
68
+
69
+
70
+ def check_ssrf_url(url: str) -> None:
71
+ """Checks if a given URL is vulnerable to SSRF. Raises HTTPException if unsafe."""
72
+ try:
73
+ parsed_url = urlparse(url)
74
+ if parsed_url.scheme not in ["http", "https"]:
75
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only HTTP/HTTPS URLs are allowed.")
76
+
77
+ hostname = parsed_url.hostname
78
+ if not hostname:
79
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid URL hostname.")
80
+
81
+ ip_info = socket.getaddrinfo(hostname, parsed_url.port)
82
+ ip_address_str = ip_info[0][4][0]
83
+ ip = ipaddress.ip_address(ip_address_str)
84
+
85
+ if not ip.is_global:
86
+ raise HTTPException(
87
+ status_code=status.HTTP_403_FORBIDDEN,
88
+ detail="Access to private or reserved IP addresses is not allowed.",
89
+ )
90
+
91
+ except socket.gaierror:
92
+ raise HTTPException(
93
+ status_code=status.HTTP_400_BAD_REQUEST, detail=f"Could not resolve hostname: {parsed_url.hostname}"
94
+ )
95
+ except Exception as e:
96
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid URL: {e}")
src/llamafactory/api/protocol.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import time
16
+ from enum import StrEnum, unique
17
+ from typing import Any, Literal
18
+
19
+ from pydantic import BaseModel, Field
20
+
21
+
22
+ @unique
23
+ class Role(StrEnum):
24
+ USER = "user"
25
+ ASSISTANT = "assistant"
26
+ SYSTEM = "system"
27
+ FUNCTION = "function"
28
+ TOOL = "tool"
29
+
30
+
31
+ @unique
32
+ class Finish(StrEnum):
33
+ STOP = "stop"
34
+ LENGTH = "length"
35
+ TOOL = "tool_calls"
36
+
37
+
38
+ class ModelCard(BaseModel):
39
+ id: str
40
+ object: Literal["model"] = "model"
41
+ created: int = Field(default_factory=lambda: int(time.time()))
42
+ owned_by: Literal["owner"] = "owner"
43
+
44
+
45
+ class ModelList(BaseModel):
46
+ object: Literal["list"] = "list"
47
+ data: list[ModelCard] = []
48
+
49
+
50
+ class Function(BaseModel):
51
+ name: str
52
+ arguments: str
53
+
54
+
55
+ class FunctionDefinition(BaseModel):
56
+ name: str
57
+ description: str
58
+ parameters: dict[str, Any]
59
+
60
+
61
+ class FunctionAvailable(BaseModel):
62
+ type: Literal["function", "code_interpreter"] = "function"
63
+ function: FunctionDefinition | None = None
64
+
65
+
66
+ class FunctionCall(BaseModel):
67
+ id: str
68
+ type: Literal["function"] = "function"
69
+ function: Function
70
+
71
+
72
+ class URL(BaseModel):
73
+ url: str
74
+ detail: Literal["auto", "low", "high"] = "auto"
75
+
76
+
77
+ class MultimodalInputItem(BaseModel):
78
+ type: Literal["text", "image_url", "video_url", "audio_url"]
79
+ text: str | None = None
80
+ image_url: URL | None = None
81
+ video_url: URL | None = None
82
+ audio_url: URL | None = None
83
+
84
+
85
+ class ChatMessage(BaseModel):
86
+ role: Role
87
+ content: str | list[MultimodalInputItem] | None = None
88
+ tool_calls: list[FunctionCall] | None = None
89
+
90
+
91
+ class ChatCompletionMessage(BaseModel):
92
+ role: Role | None = None
93
+ content: str | None = None
94
+ tool_calls: list[FunctionCall] | None = None
95
+
96
+
97
+ class ChatCompletionRequest(BaseModel):
98
+ model: str
99
+ messages: list[ChatMessage]
100
+ tools: list[FunctionAvailable] | None = None
101
+ do_sample: bool | None = None
102
+ temperature: float | None = None
103
+ top_p: float | None = None
104
+ n: int = 1
105
+ presence_penalty: float | None = None
106
+ max_tokens: int | None = None
107
+ stop: str | list[str] | None = None
108
+ stream: bool = False
109
+
110
+
111
+ class ChatCompletionResponseChoice(BaseModel):
112
+ index: int
113
+ message: ChatCompletionMessage
114
+ finish_reason: Finish
115
+
116
+
117
+ class ChatCompletionStreamResponseChoice(BaseModel):
118
+ index: int
119
+ delta: ChatCompletionMessage
120
+ finish_reason: Finish | None = None
121
+
122
+
123
+ class ChatCompletionResponseUsage(BaseModel):
124
+ prompt_tokens: int
125
+ completion_tokens: int
126
+ total_tokens: int
127
+
128
+
129
+ class ChatCompletionResponse(BaseModel):
130
+ id: str
131
+ object: Literal["chat.completion"] = "chat.completion"
132
+ created: int = Field(default_factory=lambda: int(time.time()))
133
+ model: str
134
+ choices: list[ChatCompletionResponseChoice]
135
+ usage: ChatCompletionResponseUsage
136
+
137
+
138
+ class ChatCompletionStreamResponse(BaseModel):
139
+ id: str
140
+ object: Literal["chat.completion.chunk"] = "chat.completion.chunk"
141
+ created: int = Field(default_factory=lambda: int(time.time()))
142
+ model: str
143
+ choices: list[ChatCompletionStreamResponseChoice]
144
+
145
+
146
+ class ScoreEvaluationRequest(BaseModel):
147
+ model: str
148
+ messages: list[str]
149
+ max_length: int | None = None
150
+
151
+
152
+ class ScoreEvaluationResponse(BaseModel):
153
+ id: str
154
+ object: Literal["score.evaluation"] = "score.evaluation"
155
+ model: str
156
+ scores: list[float]
src/llamafactory/chat/__init__.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from .base_engine import BaseEngine
16
+ from .chat_model import ChatModel
17
+
18
+
19
+ __all__ = ["BaseEngine", "ChatModel"]
src/llamafactory/chat/base_engine.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from abc import ABC, abstractmethod
16
+ from collections.abc import AsyncGenerator
17
+ from dataclasses import dataclass
18
+ from typing import TYPE_CHECKING, Any, Literal, Optional, Union
19
+
20
+
21
+ if TYPE_CHECKING:
22
+ from transformers import PreTrainedModel, PreTrainedTokenizer
23
+ from vllm import AsyncLLMEngine
24
+
25
+ from ..data import Template
26
+ from ..data.mm_plugin import AudioInput, ImageInput, VideoInput
27
+ from ..extras.constants import EngineName
28
+ from ..hparams import DataArguments, FinetuningArguments, GeneratingArguments, ModelArguments
29
+
30
+
31
+ @dataclass
32
+ class Response:
33
+ response_text: str
34
+ response_length: int
35
+ prompt_length: int
36
+ finish_reason: Literal["stop", "length"]
37
+
38
+
39
+ class BaseEngine(ABC):
40
+ r"""Base class for inference engine of chat models.
41
+
42
+ Must implements async methods: chat(), stream_chat() and get_scores().
43
+ """
44
+
45
+ name: "EngineName"
46
+ model: Union["PreTrainedModel", "AsyncLLMEngine"]
47
+ tokenizer: "PreTrainedTokenizer"
48
+ can_generate: bool
49
+ template: "Template"
50
+ generating_args: dict[str, Any]
51
+
52
+ @abstractmethod
53
+ def __init__(
54
+ self,
55
+ model_args: "ModelArguments",
56
+ data_args: "DataArguments",
57
+ finetuning_args: "FinetuningArguments",
58
+ generating_args: "GeneratingArguments",
59
+ ) -> None:
60
+ r"""Initialize an inference engine."""
61
+ ...
62
+
63
+ @abstractmethod
64
+ async def chat(
65
+ self,
66
+ messages: list[dict[str, str]],
67
+ system: Optional[str] = None,
68
+ tools: Optional[str] = None,
69
+ images: Optional[list["ImageInput"]] = None,
70
+ videos: Optional[list["VideoInput"]] = None,
71
+ audios: Optional[list["AudioInput"]] = None,
72
+ **input_kwargs,
73
+ ) -> list["Response"]:
74
+ r"""Get a list of responses of the chat model."""
75
+ ...
76
+
77
+ @abstractmethod
78
+ async def stream_chat(
79
+ self,
80
+ messages: list[dict[str, str]],
81
+ system: Optional[str] = None,
82
+ tools: Optional[str] = None,
83
+ images: Optional[list["ImageInput"]] = None,
84
+ videos: Optional[list["VideoInput"]] = None,
85
+ audios: Optional[list["AudioInput"]] = None,
86
+ **input_kwargs,
87
+ ) -> AsyncGenerator[str, None]:
88
+ r"""Get the response token-by-token of the chat model."""
89
+ ...
90
+
91
+ @abstractmethod
92
+ async def get_scores(
93
+ self,
94
+ batch_input: list[str],
95
+ **input_kwargs,
96
+ ) -> list[float]:
97
+ r"""Get a list of scores of the reward model."""
98
+ ...
src/llamafactory/chat/chat_model.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 THUDM and the LlamaFactory team.
2
+ #
3
+ # This code is inspired by the THUDM's ChatGLM implementation.
4
+ # https://github.com/THUDM/ChatGLM-6B/blob/main/cli_demo.py
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ import asyncio
19
+ import os
20
+ from collections.abc import AsyncGenerator, Generator
21
+ from threading import Thread
22
+ from typing import TYPE_CHECKING, Any, Optional
23
+
24
+ from ..extras.constants import EngineName
25
+ from ..extras.misc import torch_gc
26
+ from ..hparams import get_infer_args
27
+
28
+
29
+ if TYPE_CHECKING:
30
+ from ..data.mm_plugin import AudioInput, ImageInput, VideoInput
31
+ from .base_engine import BaseEngine, Response
32
+
33
+
34
+ def _start_background_loop(loop: "asyncio.AbstractEventLoop") -> None:
35
+ asyncio.set_event_loop(loop)
36
+ loop.run_forever()
37
+
38
+
39
+ class ChatModel:
40
+ r"""General class for chat models. Backed by huggingface or vllm engines.
41
+
42
+ Supports both sync and async methods.
43
+ Sync methods: chat(), stream_chat() and get_scores().
44
+ Async methods: achat(), astream_chat() and aget_scores().
45
+ """
46
+
47
+ def __init__(self, args: Optional[dict[str, Any]] = None) -> None:
48
+ model_args, data_args, finetuning_args, generating_args = get_infer_args(args)
49
+
50
+ if model_args.infer_backend == EngineName.HF:
51
+ from .hf_engine import HuggingfaceEngine
52
+
53
+ self.engine: BaseEngine = HuggingfaceEngine(model_args, data_args, finetuning_args, generating_args)
54
+ elif model_args.infer_backend == EngineName.VLLM:
55
+ try:
56
+ from .vllm_engine import VllmEngine
57
+
58
+ self.engine: BaseEngine = VllmEngine(model_args, data_args, finetuning_args, generating_args)
59
+ except ImportError as e:
60
+ raise ImportError(
61
+ "vLLM not install, you may need to run `pip install vllm`\n"
62
+ "or try to use HuggingFace backend: --infer_backend huggingface"
63
+ ) from e
64
+ elif model_args.infer_backend == EngineName.SGLANG:
65
+ try:
66
+ from .sglang_engine import SGLangEngine
67
+
68
+ self.engine: BaseEngine = SGLangEngine(model_args, data_args, finetuning_args, generating_args)
69
+ except ImportError as e:
70
+ raise ImportError(
71
+ "SGLang not install, you may need to run `pip install sglang[all]`\n"
72
+ "or try to use HuggingFace backend: --infer_backend huggingface"
73
+ ) from e
74
+ else:
75
+ raise NotImplementedError(f"Unknown backend: {model_args.infer_backend}")
76
+
77
+ self._loop = asyncio.new_event_loop()
78
+ self._thread = Thread(target=_start_background_loop, args=(self._loop,), daemon=True)
79
+ self._thread.start()
80
+
81
+ def chat(
82
+ self,
83
+ messages: list[dict[str, str]],
84
+ system: Optional[str] = None,
85
+ tools: Optional[str] = None,
86
+ images: Optional[list["ImageInput"]] = None,
87
+ videos: Optional[list["VideoInput"]] = None,
88
+ audios: Optional[list["AudioInput"]] = None,
89
+ **input_kwargs,
90
+ ) -> list["Response"]:
91
+ r"""Get a list of responses of the chat model."""
92
+ task = asyncio.run_coroutine_threadsafe(
93
+ self.achat(messages, system, tools, images, videos, audios, **input_kwargs), self._loop
94
+ )
95
+ return task.result()
96
+
97
+ async def achat(
98
+ self,
99
+ messages: list[dict[str, str]],
100
+ system: Optional[str] = None,
101
+ tools: Optional[str] = None,
102
+ images: Optional[list["ImageInput"]] = None,
103
+ videos: Optional[list["VideoInput"]] = None,
104
+ audios: Optional[list["AudioInput"]] = None,
105
+ **input_kwargs,
106
+ ) -> list["Response"]:
107
+ r"""Asynchronously get a list of responses of the chat model."""
108
+ return await self.engine.chat(messages, system, tools, images, videos, audios, **input_kwargs)
109
+
110
+ def stream_chat(
111
+ self,
112
+ messages: list[dict[str, str]],
113
+ system: Optional[str] = None,
114
+ tools: Optional[str] = None,
115
+ images: Optional[list["ImageInput"]] = None,
116
+ videos: Optional[list["VideoInput"]] = None,
117
+ audios: Optional[list["AudioInput"]] = None,
118
+ **input_kwargs,
119
+ ) -> Generator[str, None, None]:
120
+ r"""Get the response token-by-token of the chat model."""
121
+ generator = self.astream_chat(messages, system, tools, images, videos, audios, **input_kwargs)
122
+ while True:
123
+ try:
124
+ task = asyncio.run_coroutine_threadsafe(generator.__anext__(), self._loop)
125
+ yield task.result()
126
+ except StopAsyncIteration:
127
+ break
128
+
129
+ async def astream_chat(
130
+ self,
131
+ messages: list[dict[str, str]],
132
+ system: Optional[str] = None,
133
+ tools: Optional[str] = None,
134
+ images: Optional[list["ImageInput"]] = None,
135
+ videos: Optional[list["VideoInput"]] = None,
136
+ audios: Optional[list["AudioInput"]] = None,
137
+ **input_kwargs,
138
+ ) -> AsyncGenerator[str, None]:
139
+ r"""Asynchronously get the response token-by-token of the chat model."""
140
+ async for new_token in self.engine.stream_chat(
141
+ messages, system, tools, images, videos, audios, **input_kwargs
142
+ ):
143
+ yield new_token
144
+
145
+ def get_scores(
146
+ self,
147
+ batch_input: list[str],
148
+ **input_kwargs,
149
+ ) -> list[float]:
150
+ r"""Get a list of scores of the reward model."""
151
+ task = asyncio.run_coroutine_threadsafe(self.aget_scores(batch_input, **input_kwargs), self._loop)
152
+ return task.result()
153
+
154
+ async def aget_scores(
155
+ self,
156
+ batch_input: list[str],
157
+ **input_kwargs,
158
+ ) -> list[float]:
159
+ r"""Asynchronously get a list of scores of the reward model."""
160
+ return await self.engine.get_scores(batch_input, **input_kwargs)
161
+
162
+
163
+ def run_chat() -> None:
164
+ if os.name != "nt":
165
+ try:
166
+ import readline # noqa: F401
167
+ except ImportError:
168
+ print("Install `readline` for a better experience.")
169
+
170
+ chat_model = ChatModel()
171
+ messages = []
172
+ print("Welcome to the CLI application, use `clear` to remove the history, use `exit` to exit the application.")
173
+
174
+ while True:
175
+ try:
176
+ query = input("\nUser: ")
177
+ except UnicodeDecodeError:
178
+ print("Detected decoding error at the inputs, please set the terminal encoding to utf-8.")
179
+ continue
180
+ except Exception:
181
+ raise
182
+
183
+ if query.strip() == "exit":
184
+ break
185
+
186
+ if query.strip() == "clear":
187
+ messages = []
188
+ torch_gc()
189
+ print("History has been removed.")
190
+ continue
191
+
192
+ messages.append({"role": "user", "content": query})
193
+ print("Assistant: ", end="", flush=True)
194
+
195
+ response = ""
196
+ for new_text in chat_model.stream_chat(messages):
197
+ print(new_text, end="", flush=True)
198
+ response += new_text
199
+ print()
200
+ messages.append({"role": "assistant", "content": response})
src/llamafactory/chat/hf_engine.py ADDED
@@ -0,0 +1,423 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import asyncio
16
+ import os
17
+ from collections.abc import AsyncGenerator, Callable
18
+ from threading import Thread
19
+ from typing import TYPE_CHECKING, Any, Optional, Union
20
+
21
+ import torch
22
+ from transformers import GenerationConfig, TextIteratorStreamer, set_seed
23
+ from typing_extensions import override
24
+
25
+ from ..data import get_template_and_fix_tokenizer
26
+ from ..extras import logging
27
+ from ..extras.constants import AUDIO_PLACEHOLDER, IMAGE_PLACEHOLDER, VIDEO_PLACEHOLDER, EngineName
28
+ from ..model import load_model, load_tokenizer
29
+ from .base_engine import BaseEngine, Response
30
+
31
+
32
+ if TYPE_CHECKING:
33
+ from transformers import PreTrainedModel, PreTrainedTokenizer, ProcessorMixin
34
+ from trl import PreTrainedModelWrapper
35
+
36
+ from ..data import Template
37
+ from ..data.mm_plugin import AudioInput, ImageInput, VideoInput
38
+ from ..hparams import DataArguments, FinetuningArguments, GeneratingArguments, ModelArguments
39
+
40
+
41
+ logger = logging.get_logger(__name__)
42
+
43
+
44
+ class HuggingfaceEngine(BaseEngine):
45
+ def __init__(
46
+ self,
47
+ model_args: "ModelArguments",
48
+ data_args: "DataArguments",
49
+ finetuning_args: "FinetuningArguments",
50
+ generating_args: "GeneratingArguments",
51
+ ) -> None:
52
+ self.name = EngineName.HF
53
+ self.can_generate = finetuning_args.stage == "sft"
54
+ tokenizer_module = load_tokenizer(model_args)
55
+ self.tokenizer = tokenizer_module["tokenizer"]
56
+ self.processor = tokenizer_module["processor"]
57
+ self.tokenizer.padding_side = "left" if self.can_generate else "right"
58
+ self.template = get_template_and_fix_tokenizer(self.tokenizer, data_args)
59
+ self.model = load_model(
60
+ self.tokenizer, model_args, finetuning_args, is_trainable=False, add_valuehead=(not self.can_generate)
61
+ ) # must after fixing tokenizer to resize vocab
62
+ self.generating_args = generating_args.to_dict()
63
+ try:
64
+ asyncio.get_event_loop()
65
+ except RuntimeError:
66
+ logger.warning_rank0_once("There is no current event loop, creating a new one.")
67
+ loop = asyncio.new_event_loop()
68
+ asyncio.set_event_loop(loop)
69
+
70
+ self.semaphore = asyncio.Semaphore(int(os.getenv("MAX_CONCURRENT", "1")))
71
+
72
+ @staticmethod
73
+ def _process_args(
74
+ model: "PreTrainedModel",
75
+ tokenizer: "PreTrainedTokenizer",
76
+ processor: Optional["ProcessorMixin"],
77
+ template: "Template",
78
+ generating_args: dict[str, Any],
79
+ messages: list[dict[str, str]],
80
+ system: Optional[str] = None,
81
+ tools: Optional[str] = None,
82
+ images: Optional[list["ImageInput"]] = None,
83
+ videos: Optional[list["VideoInput"]] = None,
84
+ audios: Optional[list["AudioInput"]] = None,
85
+ input_kwargs: Optional[dict[str, Any]] = {},
86
+ ) -> tuple[dict[str, Any], int]:
87
+ mm_input_dict = {"images": [], "videos": [], "audios": [], "imglens": [0], "vidlens": [0], "audlens": [0]}
88
+ if images is not None:
89
+ mm_input_dict.update({"images": images, "imglens": [len(images)]})
90
+ if not any(IMAGE_PLACEHOLDER in message["content"] for message in messages):
91
+ messages[0]["content"] = IMAGE_PLACEHOLDER * len(images) + messages[0]["content"]
92
+
93
+ if videos is not None:
94
+ mm_input_dict.update({"videos": videos, "vidlens": [len(videos)]})
95
+ if not any(VIDEO_PLACEHOLDER in message["content"] for message in messages):
96
+ messages[0]["content"] = VIDEO_PLACEHOLDER * len(videos) + messages[0]["content"]
97
+
98
+ if audios is not None:
99
+ mm_input_dict.update({"audios": audios, "audlens": [len(audios)]})
100
+ if not any(AUDIO_PLACEHOLDER in message["content"] for message in messages):
101
+ messages[0]["content"] = AUDIO_PLACEHOLDER * len(audios) + messages[0]["content"]
102
+
103
+ messages = template.mm_plugin.process_messages(
104
+ messages, mm_input_dict["images"], mm_input_dict["videos"], mm_input_dict["audios"], processor
105
+ )
106
+ paired_messages = messages + [{"role": "assistant", "content": ""}]
107
+ prompt_ids, _ = template.encode_oneturn(tokenizer, paired_messages, system, tools)
108
+ prompt_ids, _ = template.mm_plugin.process_token_ids(
109
+ prompt_ids,
110
+ None,
111
+ mm_input_dict["images"],
112
+ mm_input_dict["videos"],
113
+ mm_input_dict["audios"],
114
+ tokenizer,
115
+ processor,
116
+ )
117
+ prompt_length = len(prompt_ids)
118
+ inputs = torch.tensor([prompt_ids], device=model.device)
119
+ attention_mask = torch.ones_like(inputs, dtype=torch.long)
120
+
121
+ do_sample: Optional[bool] = input_kwargs.pop("do_sample", None)
122
+ temperature: Optional[float] = input_kwargs.pop("temperature", None)
123
+ top_p: Optional[float] = input_kwargs.pop("top_p", None)
124
+ top_k: Optional[float] = input_kwargs.pop("top_k", None)
125
+ num_return_sequences: int = input_kwargs.pop("num_return_sequences", 1)
126
+ repetition_penalty: Optional[float] = input_kwargs.pop("repetition_penalty", None)
127
+ length_penalty: Optional[float] = input_kwargs.pop("length_penalty", None)
128
+ skip_special_tokens: Optional[bool] = input_kwargs.pop("skip_special_tokens", None)
129
+ max_length: Optional[int] = input_kwargs.pop("max_length", None)
130
+ max_new_tokens: Optional[int] = input_kwargs.pop("max_new_tokens", None)
131
+ seed: Optional[int] = input_kwargs.pop("seed", None)
132
+ stop: Optional[Union[str, list[str]]] = input_kwargs.pop("stop", None)
133
+
134
+ if stop is not None:
135
+ logger.warning_rank0("Stop parameter is not supported by the huggingface engine yet.")
136
+
137
+ generating_args = generating_args.copy()
138
+ generating_args.update(
139
+ dict(
140
+ do_sample=do_sample if do_sample is not None else generating_args["do_sample"],
141
+ temperature=temperature if temperature is not None else generating_args["temperature"],
142
+ top_p=top_p if top_p is not None else generating_args["top_p"],
143
+ top_k=top_k if top_k is not None else generating_args["top_k"],
144
+ num_return_sequences=num_return_sequences,
145
+ repetition_penalty=repetition_penalty
146
+ if repetition_penalty is not None
147
+ else generating_args["repetition_penalty"],
148
+ length_penalty=length_penalty if length_penalty is not None else generating_args["length_penalty"],
149
+ skip_special_tokens=skip_special_tokens
150
+ if skip_special_tokens is not None
151
+ else generating_args["skip_special_tokens"],
152
+ eos_token_id=template.get_stop_token_ids(tokenizer),
153
+ pad_token_id=tokenizer.pad_token_id,
154
+ )
155
+ )
156
+
157
+ if isinstance(num_return_sequences, int) and num_return_sequences > 1: # do_sample needs temperature > 0
158
+ generating_args["do_sample"] = True
159
+ generating_args["temperature"] = generating_args["temperature"] or 1.0
160
+
161
+ if not generating_args["temperature"]:
162
+ generating_args["do_sample"] = False
163
+
164
+ if not generating_args["do_sample"]:
165
+ generating_args.pop("temperature", None)
166
+ generating_args.pop("top_p", None)
167
+
168
+ if max_length:
169
+ generating_args.pop("max_new_tokens", None)
170
+ generating_args["max_length"] = max_length
171
+
172
+ if max_new_tokens:
173
+ generating_args.pop("max_length", None)
174
+ generating_args["max_new_tokens"] = max_new_tokens
175
+
176
+ gen_kwargs = dict(
177
+ inputs=inputs,
178
+ attention_mask=attention_mask,
179
+ generation_config=GenerationConfig(**generating_args),
180
+ )
181
+ if seed is not None:
182
+ gen_kwargs["_seed"] = seed
183
+
184
+ mm_inputs = template.mm_plugin.get_mm_inputs(**mm_input_dict, batch_ids=[prompt_ids], processor=processor)
185
+ for key, value in mm_inputs.items():
186
+ if isinstance(value, list) and isinstance(value[0], torch.Tensor): # for pixtral inputs
187
+ value = torch.stack(value) # assume they have same sizes
188
+ elif (
189
+ isinstance(value, list) and isinstance(value[0], list) and isinstance(value[0][0], torch.Tensor)
190
+ ): # for minicpmv inputs
191
+ value = torch.stack([torch.stack(v) for v in value])
192
+ elif not isinstance(value, torch.Tensor):
193
+ value = torch.tensor(value)
194
+
195
+ if torch.is_floating_point(value): # cast data dtype for paligemma
196
+ value = value.to(model.dtype)
197
+
198
+ if key == "second_per_grid_ts": # qwen2.5vl special case
199
+ gen_kwargs[key] = value.tolist()
200
+ else:
201
+ gen_kwargs[key] = value.to(model.device)
202
+
203
+ if getattr(model.config, "model_type", None) in ["minicpmv", "minicpmo"]:
204
+ gen_kwargs["input_ids"] = inputs
205
+ gen_kwargs["tokenizer"] = tokenizer
206
+ if "audio_feature_lens" in mm_inputs:
207
+ gen_kwargs["audio_feature_lens"] = mm_inputs["audio_feature_lens"]
208
+
209
+ gen_kwargs.pop("image_sizes", None)
210
+
211
+ return gen_kwargs, prompt_length
212
+
213
+ @staticmethod
214
+ @torch.inference_mode()
215
+ def _chat(
216
+ model: "PreTrainedModel",
217
+ tokenizer: "PreTrainedTokenizer",
218
+ processor: Optional["ProcessorMixin"],
219
+ template: "Template",
220
+ generating_args: dict[str, Any],
221
+ messages: list[dict[str, str]],
222
+ system: Optional[str] = None,
223
+ tools: Optional[str] = None,
224
+ images: Optional[list["ImageInput"]] = None,
225
+ videos: Optional[list["VideoInput"]] = None,
226
+ audios: Optional[list["AudioInput"]] = None,
227
+ input_kwargs: Optional[dict[str, Any]] = {},
228
+ ) -> list["Response"]:
229
+ gen_kwargs, prompt_length = HuggingfaceEngine._process_args(
230
+ model,
231
+ tokenizer,
232
+ processor,
233
+ template,
234
+ generating_args,
235
+ messages,
236
+ system,
237
+ tools,
238
+ images,
239
+ videos,
240
+ audios,
241
+ input_kwargs,
242
+ )
243
+ seed = gen_kwargs.pop("_seed", None)
244
+ if seed is not None:
245
+ set_seed(seed)
246
+
247
+ generate_output = model.generate(**gen_kwargs)
248
+ if isinstance(generate_output, tuple):
249
+ generate_output = generate_output[1][0] # post-process the minicpm_o output
250
+
251
+ response_ids = generate_output[:, prompt_length:]
252
+ response = tokenizer.batch_decode(
253
+ response_ids,
254
+ skip_special_tokens=getattr(gen_kwargs["generation_config"], "skip_special_tokens", True),
255
+ clean_up_tokenization_spaces=True,
256
+ )
257
+ results = []
258
+ for i in range(len(response)):
259
+ eos_index = (response_ids[i] == tokenizer.eos_token_id).nonzero()
260
+ response_length = (eos_index[0].item() + 1) if len(eos_index) else len(response_ids[i])
261
+ results.append(
262
+ Response(
263
+ response_text=response[i],
264
+ response_length=response_length,
265
+ prompt_length=prompt_length,
266
+ finish_reason="stop" if len(eos_index) else "length",
267
+ )
268
+ )
269
+
270
+ return results
271
+
272
+ @staticmethod
273
+ @torch.inference_mode()
274
+ def _stream_chat(
275
+ model: "PreTrainedModel",
276
+ tokenizer: "PreTrainedTokenizer",
277
+ processor: Optional["ProcessorMixin"],
278
+ template: "Template",
279
+ generating_args: dict[str, Any],
280
+ messages: list[dict[str, str]],
281
+ system: Optional[str] = None,
282
+ tools: Optional[str] = None,
283
+ images: Optional[list["ImageInput"]] = None,
284
+ videos: Optional[list["VideoInput"]] = None,
285
+ audios: Optional[list["AudioInput"]] = None,
286
+ input_kwargs: Optional[dict[str, Any]] = {},
287
+ ) -> Callable[[], str]:
288
+ gen_kwargs, _ = HuggingfaceEngine._process_args(
289
+ model,
290
+ tokenizer,
291
+ processor,
292
+ template,
293
+ generating_args,
294
+ messages,
295
+ system,
296
+ tools,
297
+ images,
298
+ videos,
299
+ audios,
300
+ input_kwargs,
301
+ )
302
+ seed = gen_kwargs.pop("_seed", None)
303
+ if seed is not None:
304
+ set_seed(seed)
305
+
306
+ streamer = TextIteratorStreamer(
307
+ tokenizer,
308
+ skip_prompt=True,
309
+ skip_special_tokens=getattr(gen_kwargs["generation_config"], "skip_special_tokens", True),
310
+ )
311
+ gen_kwargs["streamer"] = streamer
312
+ thread = Thread(target=model.generate, kwargs=gen_kwargs, daemon=True)
313
+ thread.start()
314
+
315
+ def stream():
316
+ try:
317
+ return streamer.__next__()
318
+ except StopIteration:
319
+ raise StopAsyncIteration()
320
+
321
+ return stream
322
+
323
+ @staticmethod
324
+ @torch.inference_mode()
325
+ def _get_scores(
326
+ model: "PreTrainedModelWrapper",
327
+ tokenizer: "PreTrainedTokenizer",
328
+ batch_input: list[str],
329
+ input_kwargs: Optional[dict[str, Any]] = {},
330
+ ) -> list[float]:
331
+ max_length: Optional[int] = input_kwargs.pop("max_length", None)
332
+ device = getattr(model.pretrained_model, "device", "cuda")
333
+ inputs: dict[str, torch.Tensor] = tokenizer(
334
+ batch_input,
335
+ padding=True,
336
+ truncation=True,
337
+ max_length=max_length or getattr(model.config, "max_position_embeddings", 1024),
338
+ return_tensors="pt",
339
+ add_special_tokens=False,
340
+ ).to(device)
341
+ values: torch.Tensor = model(**inputs, return_dict=True, use_cache=False)[-1]
342
+ scores = values.gather(dim=-1, index=(inputs["attention_mask"].sum(dim=-1, keepdim=True) - 1))
343
+ return scores
344
+
345
+ @override
346
+ async def chat(
347
+ self,
348
+ messages: list[dict[str, str]],
349
+ system: Optional[str] = None,
350
+ tools: Optional[str] = None,
351
+ images: Optional[list["ImageInput"]] = None,
352
+ videos: Optional[list["VideoInput"]] = None,
353
+ audios: Optional[list["AudioInput"]] = None,
354
+ **input_kwargs,
355
+ ) -> list["Response"]:
356
+ if not self.can_generate:
357
+ raise ValueError("The current model does not support `chat`.")
358
+
359
+ input_args = (
360
+ self.model,
361
+ self.tokenizer,
362
+ self.processor,
363
+ self.template,
364
+ self.generating_args,
365
+ messages,
366
+ system,
367
+ tools,
368
+ images,
369
+ videos,
370
+ audios,
371
+ input_kwargs,
372
+ )
373
+ async with self.semaphore:
374
+ return await asyncio.to_thread(self._chat, *input_args)
375
+
376
+ @override
377
+ async def stream_chat(
378
+ self,
379
+ messages: list[dict[str, str]],
380
+ system: Optional[str] = None,
381
+ tools: Optional[str] = None,
382
+ images: Optional[list["ImageInput"]] = None,
383
+ videos: Optional[list["VideoInput"]] = None,
384
+ audios: Optional[list["AudioInput"]] = None,
385
+ **input_kwargs,
386
+ ) -> AsyncGenerator[str, None]:
387
+ if not self.can_generate:
388
+ raise ValueError("The current model does not support `stream_chat`.")
389
+
390
+ input_args = (
391
+ self.model,
392
+ self.tokenizer,
393
+ self.processor,
394
+ self.template,
395
+ self.generating_args,
396
+ messages,
397
+ system,
398
+ tools,
399
+ images,
400
+ videos,
401
+ audios,
402
+ input_kwargs,
403
+ )
404
+ async with self.semaphore:
405
+ stream = self._stream_chat(*input_args)
406
+ while True:
407
+ try:
408
+ yield await asyncio.to_thread(stream)
409
+ except StopAsyncIteration:
410
+ break
411
+
412
+ @override
413
+ async def get_scores(
414
+ self,
415
+ batch_input: list[str],
416
+ **input_kwargs,
417
+ ) -> list[float]:
418
+ if self.can_generate:
419
+ raise ValueError("Cannot get scores using an auto-regressive model.")
420
+
421
+ input_args = (self.model, self.tokenizer, batch_input, input_kwargs)
422
+ async with self.semaphore:
423
+ return await asyncio.to_thread(self._get_scores, *input_args)
src/llamafactory/chat/sglang_engine.py ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import asyncio
16
+ import atexit
17
+ import json
18
+ from collections.abc import AsyncGenerator, AsyncIterator, Sequence
19
+ from typing import TYPE_CHECKING, Any, Optional, Union
20
+
21
+ import requests
22
+ from typing_extensions import override
23
+
24
+ from ..data import get_template_and_fix_tokenizer
25
+ from ..extras import logging
26
+ from ..extras.constants import AUDIO_PLACEHOLDER, IMAGE_PLACEHOLDER, VIDEO_PLACEHOLDER, EngineName
27
+ from ..extras.misc import get_device_count, torch_gc
28
+ from ..extras.packages import is_sglang_available
29
+ from ..hparams import DataArguments, FinetuningArguments, GeneratingArguments, ModelArguments
30
+ from ..model import load_config, load_tokenizer
31
+ from ..model.model_utils.quantization import QuantizationMethod
32
+ from .base_engine import BaseEngine, Response
33
+
34
+
35
+ if is_sglang_available():
36
+ from sglang.utils import launch_server_cmd, terminate_process, wait_for_server # type: ignore
37
+
38
+
39
+ if TYPE_CHECKING:
40
+ from ..data.mm_plugin import AudioInput, ImageInput, VideoInput
41
+
42
+
43
+ logger = logging.get_logger(__name__)
44
+
45
+
46
+ class SGLangEngine(BaseEngine):
47
+ """Inference engine for SGLang models.
48
+
49
+ This class wraps the SGLang engine to provide a consistent interface for text generation
50
+ that matches LLaMA Factory's requirements. It uses the SGLang HTTP server approach for
51
+ better interaction and performance. The engine launches a server process and communicates
52
+ with it via HTTP requests.
53
+
54
+ For more details on the SGLang HTTP server approach, see:
55
+ https://docs.sglang.ai/backend/send_request.html
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ model_args: "ModelArguments",
61
+ data_args: "DataArguments",
62
+ finetuning_args: "FinetuningArguments",
63
+ generating_args: "GeneratingArguments",
64
+ ) -> None:
65
+ self.name = EngineName.SGLANG
66
+ self.model_args = model_args
67
+ config = load_config(model_args) # may download model from ms hub
68
+ if getattr(config, "quantization_config", None): # gptq models should use float16
69
+ quantization_config: dict[str, Any] = getattr(config, "quantization_config", None)
70
+ quant_method = quantization_config.get("quant_method", "")
71
+ if quant_method == QuantizationMethod.GPTQ and model_args.infer_dtype == "auto":
72
+ model_args.infer_dtype = "float16"
73
+
74
+ self.can_generate = finetuning_args.stage == "sft"
75
+ tokenizer_module = load_tokenizer(model_args)
76
+ self.tokenizer = tokenizer_module["tokenizer"]
77
+ self.processor = tokenizer_module["processor"]
78
+ self.tokenizer.padding_side = "left"
79
+ self.template = get_template_and_fix_tokenizer(self.tokenizer, data_args)
80
+ self.template.mm_plugin.expand_mm_tokens = False # for sglang generate
81
+ self.generating_args = generating_args.to_dict()
82
+ if model_args.adapter_name_or_path is not None:
83
+ self.lora_request = True
84
+ else:
85
+ self.lora_request = False
86
+
87
+ launch_cmd = [
88
+ "python3 -m sglang.launch_server",
89
+ f"--model-path {model_args.model_name_or_path}",
90
+ f"--dtype {model_args.infer_dtype}",
91
+ f"--context-length {model_args.sglang_maxlen}",
92
+ f"--mem-fraction-static {model_args.sglang_mem_fraction}",
93
+ f"--tp-size {model_args.sglang_tp_size if model_args.sglang_tp_size != -1 else get_device_count() or 1}",
94
+ f"--download-dir {model_args.cache_dir}",
95
+ "--log-level error",
96
+ ]
97
+ if self.lora_request:
98
+ launch_cmd.extend(
99
+ [
100
+ "--max-loras-per-batch 1",
101
+ f"--lora-backend {model_args.sglang_lora_backend}",
102
+ f"--lora-paths lora0={model_args.adapter_name_or_path[0]}",
103
+ "--disable-radix-cache",
104
+ ]
105
+ )
106
+ launch_cmd = " ".join(launch_cmd)
107
+ logger.info_rank0(f"Starting SGLang server with command: {launch_cmd}")
108
+ try:
109
+ torch_gc()
110
+ self.server_process, port = launch_server_cmd(launch_cmd)
111
+ self.base_url = f"http://localhost:{port}"
112
+ atexit.register(self._cleanup_server)
113
+
114
+ logger.info_rank0(f"Waiting for SGLang server to be ready at {self.base_url}")
115
+ wait_for_server(self.base_url, timeout=300)
116
+ logger.info_rank0(f"SGLang server initialized successfully at {self.base_url}")
117
+ try:
118
+ response = requests.get(f"{self.base_url}/get_model_info", timeout=5)
119
+ if response.status_code == 200:
120
+ model_info = response.json()
121
+ logger.info(f"SGLang server model info: {model_info}")
122
+ except Exception as e:
123
+ logger.debug(f"Note: could not get model info: {str(e)}")
124
+
125
+ except Exception as e:
126
+ logger.error(f"Failed to start SGLang server: {str(e)}")
127
+ self._cleanup_server() # make sure to clean up any started process
128
+ raise RuntimeError(f"SGLang server initialization failed: {str(e)}.")
129
+
130
+ def _cleanup_server(self):
131
+ r"""Clean up the server process when the engine is destroyed."""
132
+ if hasattr(self, "server_process") and self.server_process:
133
+ try:
134
+ logger.info("Terminating SGLang server process")
135
+ terminate_process(self.server_process)
136
+ logger.info("SGLang server process terminated")
137
+ except Exception as e:
138
+ logger.warning(f"Error terminating SGLang server: {str(e)}")
139
+
140
+ async def _generate(
141
+ self,
142
+ messages: list[dict[str, str]],
143
+ system: Optional[str] = None,
144
+ tools: Optional[str] = None,
145
+ images: Optional[list["ImageInput"]] = None,
146
+ videos: Optional[list["VideoInput"]] = None,
147
+ audios: Optional[list["AudioInput"]] = None,
148
+ **input_kwargs,
149
+ ) -> AsyncIterator[dict[str, Any]]:
150
+ if images is not None and not any(IMAGE_PLACEHOLDER in message["content"] for message in messages):
151
+ messages[0]["content"] = IMAGE_PLACEHOLDER * len(images) + messages[0]["content"]
152
+
153
+ if videos is not None and not any(VIDEO_PLACEHOLDER in message["content"] for message in messages):
154
+ messages[0]["content"] = VIDEO_PLACEHOLDER * len(videos) + messages[0]["content"]
155
+
156
+ if audios is not None and not any(AUDIO_PLACEHOLDER in message["content"] for message in messages):
157
+ messages[0]["content"] = AUDIO_PLACEHOLDER * len(audios) + messages[0]["content"]
158
+
159
+ messages = self.template.mm_plugin.process_messages(
160
+ messages, images or [], videos or [], audios or [], self.processor
161
+ )
162
+ paired_messages = messages + [{"role": "assistant", "content": ""}]
163
+ prompt_ids, _ = self.template.encode_oneturn(self.tokenizer, paired_messages, system, tools)
164
+ prompt_length = len(prompt_ids)
165
+
166
+ temperature: Optional[float] = input_kwargs.pop("temperature", None)
167
+ top_p: Optional[float] = input_kwargs.pop("top_p", None)
168
+ top_k: Optional[float] = input_kwargs.pop("top_k", None)
169
+ num_return_sequences: int = input_kwargs.pop("num_return_sequences", 1)
170
+ repetition_penalty: Optional[float] = input_kwargs.pop("repetition_penalty", None)
171
+ skip_special_tokens: Optional[bool] = input_kwargs.pop("skip_special_tokens", None)
172
+ max_length: Optional[int] = input_kwargs.pop("max_length", None)
173
+ max_new_tokens: Optional[int] = input_kwargs.pop("max_new_tokens", None)
174
+ seed: Optional[int] = input_kwargs.pop("seed", None)
175
+ stop: Optional[Union[str, list[str]]] = input_kwargs.pop("stop", None)
176
+
177
+ if num_return_sequences != 1:
178
+ raise NotImplementedError("SGLang only supports n=1.")
179
+
180
+ if "max_new_tokens" in self.generating_args:
181
+ max_tokens = self.generating_args["max_new_tokens"]
182
+ elif "max_length" in self.generating_args:
183
+ if self.generating_args["max_length"] > prompt_length:
184
+ max_tokens = self.generating_args["max_length"] - prompt_length
185
+ else:
186
+ max_tokens = 1
187
+
188
+ if max_length:
189
+ max_tokens = max_length - prompt_length if max_length > prompt_length else 1
190
+
191
+ if max_new_tokens:
192
+ max_tokens = max_new_tokens
193
+
194
+ sampling_params = {
195
+ "temperature": temperature if temperature is not None else self.generating_args["temperature"],
196
+ "top_p": (top_p if top_p is not None else self.generating_args["top_p"]) or 1.0, # top_p must > 0
197
+ "top_k": (top_k if top_k is not None else self.generating_args["top_k"]) or -1, # top_k must > 0
198
+ "stop": stop,
199
+ "stop_token_ids": self.template.get_stop_token_ids(self.tokenizer),
200
+ "max_new_tokens": max_tokens,
201
+ "repetition_penalty": (
202
+ repetition_penalty if repetition_penalty is not None else self.generating_args["repetition_penalty"]
203
+ )
204
+ or 1.0, # repetition_penalty must > 0
205
+ "skip_special_tokens": skip_special_tokens
206
+ if skip_special_tokens is not None
207
+ else self.generating_args["skip_special_tokens"],
208
+ }
209
+ if seed is not None:
210
+ sampling_params["seed"] = seed
211
+
212
+ def stream_request():
213
+ json_data = {
214
+ "input_ids": prompt_ids,
215
+ "sampling_params": sampling_params,
216
+ "stream": True,
217
+ }
218
+ if self.lora_request:
219
+ json_data["lora_request"] = ["lora0"]
220
+ response = requests.post(f"{self.base_url}/generate", json=json_data, stream=True)
221
+ if response.status_code != 200:
222
+ raise RuntimeError(f"SGLang server error: {response.status_code}, {response.text}")
223
+
224
+ for chunk in response.iter_lines(decode_unicode=False):
225
+ chunk = str(chunk.decode("utf-8"))
226
+ if chunk == "data: [DONE]":
227
+ break
228
+
229
+ if chunk and chunk.startswith("data:"):
230
+ yield json.loads(chunk[5:].strip("\n"))
231
+
232
+ return await asyncio.to_thread(stream_request)
233
+
234
+ @override
235
+ async def chat(
236
+ self,
237
+ messages: Sequence[dict[str, str]],
238
+ system: Optional[str] = None,
239
+ tools: Optional[str] = None,
240
+ images: Optional[Sequence["ImageInput"]] = None,
241
+ videos: Optional[Sequence["VideoInput"]] = None,
242
+ audios: Optional[Sequence["AudioInput"]] = None,
243
+ **input_kwargs,
244
+ ) -> list["Response"]:
245
+ final_output = None
246
+ generator = await self._generate(messages, system, tools, images, videos, audios, **input_kwargs)
247
+ for request_output in generator:
248
+ final_output = request_output
249
+
250
+ results = [
251
+ Response(
252
+ response_text=final_output["text"],
253
+ response_length=final_output["meta_info"]["completion_tokens"],
254
+ prompt_length=final_output["meta_info"]["prompt_tokens"],
255
+ finish_reason="stop" if final_output["meta_info"]["finish_reason"] == "stop" else "length",
256
+ )
257
+ ]
258
+ return results
259
+
260
+ @override
261
+ async def stream_chat(
262
+ self,
263
+ messages: list[dict[str, str]],
264
+ system: Optional[str] = None,
265
+ tools: Optional[str] = None,
266
+ images: Optional[list["ImageInput"]] = None,
267
+ videos: Optional[list["VideoInput"]] = None,
268
+ audios: Optional[list["AudioInput"]] = None,
269
+ **input_kwargs,
270
+ ) -> AsyncGenerator[str, None]:
271
+ generated_text = ""
272
+ generator = await self._generate(messages, system, tools, images, videos, audios, **input_kwargs)
273
+ for result in generator:
274
+ delta_text = result["text"][len(generated_text) :]
275
+ generated_text = result["text"]
276
+ yield delta_text
277
+
278
+ @override
279
+ async def get_scores(
280
+ self,
281
+ batch_input: list[str],
282
+ **input_kwargs,
283
+ ) -> list[float]:
284
+ raise NotImplementedError("SGLang engine does not support `get_scores`.")
285
+
286
+ def __del__(self):
287
+ r"""Ensure server is cleaned up when object is deleted."""
288
+ self._cleanup_server()
289
+ try:
290
+ atexit.unregister(self._cleanup_server)
291
+ except Exception:
292
+ pass
src/llamafactory/chat/vllm_engine.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import uuid
16
+ from collections.abc import AsyncGenerator, AsyncIterator
17
+ from typing import TYPE_CHECKING, Any, Optional, Union
18
+
19
+ from packaging import version
20
+ from typing_extensions import override
21
+
22
+ from ..data import get_template_and_fix_tokenizer
23
+ from ..extras import logging
24
+ from ..extras.constants import AUDIO_PLACEHOLDER, IMAGE_PLACEHOLDER, VIDEO_PLACEHOLDER, EngineName
25
+ from ..extras.misc import get_device_count
26
+ from ..extras.packages import is_vllm_available
27
+ from ..model import load_config, load_tokenizer
28
+ from ..model.model_utils.quantization import QuantizationMethod
29
+ from ..model.model_utils.visual import LlavaMultiModalProjectorForYiVLForVLLM
30
+ from .base_engine import BaseEngine, Response
31
+
32
+
33
+ if is_vllm_available():
34
+ from vllm import AsyncEngineArgs, AsyncLLMEngine, RequestOutput, SamplingParams
35
+ from vllm.lora.request import LoRARequest
36
+
37
+
38
+ if TYPE_CHECKING:
39
+ from ..data.mm_plugin import AudioInput, ImageInput, VideoInput
40
+ from ..hparams import DataArguments, FinetuningArguments, GeneratingArguments, ModelArguments
41
+
42
+
43
+ logger = logging.get_logger(__name__)
44
+
45
+
46
+ class VllmEngine(BaseEngine):
47
+ def __init__(
48
+ self,
49
+ model_args: "ModelArguments",
50
+ data_args: "DataArguments",
51
+ finetuning_args: "FinetuningArguments",
52
+ generating_args: "GeneratingArguments",
53
+ ) -> None:
54
+ self.name = EngineName.VLLM
55
+ self.model_args = model_args
56
+ config = load_config(model_args) # may download model from ms hub
57
+ if getattr(config, "quantization_config", None): # gptq models should use float16
58
+ quantization_config: dict[str, Any] = getattr(config, "quantization_config", None)
59
+ quant_method = quantization_config.get("quant_method", "")
60
+ if quant_method == QuantizationMethod.GPTQ and model_args.infer_dtype == "auto":
61
+ model_args.infer_dtype = "float16"
62
+
63
+ self.can_generate = finetuning_args.stage == "sft"
64
+ tokenizer_module = load_tokenizer(model_args)
65
+ self.tokenizer = tokenizer_module["tokenizer"]
66
+ self.processor = tokenizer_module["processor"]
67
+ self.tokenizer.padding_side = "left"
68
+ self.template = get_template_and_fix_tokenizer(self.tokenizer, data_args)
69
+ self.template.mm_plugin.expand_mm_tokens = False # for vllm generate
70
+ self.generating_args = generating_args.to_dict()
71
+
72
+ engine_args = {
73
+ "model": model_args.model_name_or_path,
74
+ "trust_remote_code": model_args.trust_remote_code,
75
+ "download_dir": model_args.cache_dir,
76
+ "dtype": model_args.infer_dtype,
77
+ "max_model_len": model_args.vllm_maxlen,
78
+ "tensor_parallel_size": get_device_count() or 1,
79
+ "gpu_memory_utilization": model_args.vllm_gpu_util,
80
+ "disable_log_stats": True,
81
+ "enforce_eager": model_args.vllm_enforce_eager,
82
+ "enable_lora": model_args.adapter_name_or_path is not None,
83
+ "max_lora_rank": model_args.vllm_max_lora_rank,
84
+ }
85
+
86
+ import vllm
87
+
88
+ if version.parse(vllm.__version__) <= version.parse("0.10.0"):
89
+ engine_args["disable_log_requests"] = True
90
+ else:
91
+ engine_args["enable_log_requests"] = False
92
+
93
+ if self.template.mm_plugin.__class__.__name__ != "BasePlugin":
94
+ engine_args["limit_mm_per_prompt"] = {"image": 4, "video": 2, "audio": 2}
95
+
96
+ if isinstance(model_args.vllm_config, dict):
97
+ engine_args.update(model_args.vllm_config)
98
+
99
+ if getattr(config, "is_yi_vl_derived_model", None):
100
+ import vllm.model_executor.models.llava
101
+
102
+ logger.info_rank0("Detected Yi-VL model, applying projector patch.")
103
+ vllm.model_executor.models.llava.LlavaMultiModalProjector = LlavaMultiModalProjectorForYiVLForVLLM
104
+
105
+ self.model = AsyncLLMEngine.from_engine_args(AsyncEngineArgs(**engine_args))
106
+ if model_args.adapter_name_or_path is not None:
107
+ self.lora_request = LoRARequest("default", 1, model_args.adapter_name_or_path[0])
108
+ else:
109
+ self.lora_request = None
110
+
111
+ async def _generate(
112
+ self,
113
+ messages: list[dict[str, str]],
114
+ system: Optional[str] = None,
115
+ tools: Optional[str] = None,
116
+ images: Optional[list["ImageInput"]] = None,
117
+ videos: Optional[list["VideoInput"]] = None,
118
+ audios: Optional[list["AudioInput"]] = None,
119
+ **input_kwargs,
120
+ ) -> AsyncIterator["RequestOutput"]:
121
+ request_id = f"chatcmpl-{uuid.uuid4().hex}"
122
+ if images is not None and not any(IMAGE_PLACEHOLDER in message["content"] for message in messages):
123
+ messages[0]["content"] = IMAGE_PLACEHOLDER * len(images) + messages[0]["content"]
124
+
125
+ if videos is not None and not any(VIDEO_PLACEHOLDER in message["content"] for message in messages):
126
+ messages[0]["content"] = VIDEO_PLACEHOLDER * len(videos) + messages[0]["content"]
127
+
128
+ if audios is not None and not any(AUDIO_PLACEHOLDER in message["content"] for message in messages):
129
+ messages[0]["content"] = AUDIO_PLACEHOLDER * len(audios) + messages[0]["content"]
130
+
131
+ messages = self.template.mm_plugin.process_messages(
132
+ messages, images or [], videos or [], audios or [], self.processor
133
+ )
134
+ paired_messages = messages + [{"role": "assistant", "content": ""}]
135
+ prompt_ids, _ = self.template.encode_oneturn(self.tokenizer, paired_messages, system, tools)
136
+ prompt_length = len(prompt_ids)
137
+
138
+ temperature: Optional[float] = input_kwargs.pop("temperature", None)
139
+ top_p: Optional[float] = input_kwargs.pop("top_p", None)
140
+ top_k: Optional[float] = input_kwargs.pop("top_k", None)
141
+ num_return_sequences: int = input_kwargs.pop("num_return_sequences", 1)
142
+ repetition_penalty: Optional[float] = input_kwargs.pop("repetition_penalty", None)
143
+ length_penalty: Optional[float] = input_kwargs.pop("length_penalty", None)
144
+ skip_special_tokens: Optional[bool] = input_kwargs.pop("skip_special_tokens", None)
145
+ max_length: Optional[int] = input_kwargs.pop("max_length", None)
146
+ max_new_tokens: Optional[int] = input_kwargs.pop("max_new_tokens", None)
147
+ seed: Optional[int] = input_kwargs.pop("seed", None)
148
+ stop: Optional[Union[str, list[str]]] = input_kwargs.pop("stop", None)
149
+
150
+ if length_penalty is not None:
151
+ logger.warning_rank0("Length penalty is not supported by the vllm engine yet.")
152
+
153
+ if "max_new_tokens" in self.generating_args:
154
+ max_tokens = self.generating_args["max_new_tokens"]
155
+ elif "max_length" in self.generating_args:
156
+ if self.generating_args["max_length"] > prompt_length:
157
+ max_tokens = self.generating_args["max_length"] - prompt_length
158
+ else:
159
+ max_tokens = 1
160
+
161
+ if max_length:
162
+ max_tokens = max_length - prompt_length if max_length > prompt_length else 1
163
+
164
+ if max_new_tokens:
165
+ max_tokens = max_new_tokens
166
+
167
+ sampling_kwargs = dict(
168
+ n=num_return_sequences,
169
+ repetition_penalty=(
170
+ repetition_penalty if repetition_penalty is not None else self.generating_args["repetition_penalty"]
171
+ )
172
+ or 1.0, # repetition_penalty must > 0
173
+ temperature=temperature if temperature is not None else self.generating_args["temperature"],
174
+ top_p=(top_p if top_p is not None else self.generating_args["top_p"]) or 1.0, # top_p must > 0
175
+ top_k=(top_k if top_k is not None else self.generating_args["top_k"]) or -1, # top_k must > 0
176
+ stop=stop,
177
+ stop_token_ids=self.template.get_stop_token_ids(self.tokenizer),
178
+ max_tokens=max_tokens,
179
+ skip_special_tokens=skip_special_tokens
180
+ if skip_special_tokens is not None
181
+ else self.generating_args["skip_special_tokens"],
182
+ )
183
+ if seed is not None:
184
+ sampling_kwargs["seed"] = seed
185
+
186
+ sampling_params = SamplingParams(**sampling_kwargs)
187
+
188
+ multi_modal_data = {}
189
+ if images is not None: # add image features
190
+ multi_modal_data["image"] = self.template.mm_plugin._regularize_images(
191
+ images,
192
+ image_max_pixels=self.model_args.image_max_pixels,
193
+ image_min_pixels=self.model_args.image_min_pixels,
194
+ )["images"]
195
+
196
+ if videos is not None:
197
+ multi_modal_data["video"] = self.template.mm_plugin._regularize_videos(
198
+ videos,
199
+ image_max_pixels=self.model_args.video_max_pixels,
200
+ image_min_pixels=self.model_args.video_min_pixels,
201
+ video_fps=self.model_args.video_fps,
202
+ video_maxlen=self.model_args.video_maxlen,
203
+ )["videos"]
204
+
205
+ if audios is not None:
206
+ audio_data = self.template.mm_plugin._regularize_audios(
207
+ audios,
208
+ sampling_rate=self.model_args.audio_sampling_rate,
209
+ )
210
+ multi_modal_data["audio"] = zip(audio_data["audios"], audio_data["sampling_rates"])
211
+
212
+ result_generator = self.model.generate(
213
+ {"prompt_token_ids": prompt_ids, "multi_modal_data": multi_modal_data or None},
214
+ sampling_params=sampling_params,
215
+ request_id=request_id,
216
+ lora_request=self.lora_request,
217
+ )
218
+ return result_generator
219
+
220
+ @override
221
+ async def chat(
222
+ self,
223
+ messages: list[dict[str, str]],
224
+ system: Optional[str] = None,
225
+ tools: Optional[str] = None,
226
+ images: Optional[list["ImageInput"]] = None,
227
+ videos: Optional[list["VideoInput"]] = None,
228
+ audios: Optional[list["AudioInput"]] = None,
229
+ **input_kwargs,
230
+ ) -> list["Response"]:
231
+ final_output = None
232
+ generator = await self._generate(messages, system, tools, images, videos, audios, **input_kwargs)
233
+ async for request_output in generator:
234
+ final_output = request_output
235
+
236
+ results = []
237
+ for output in final_output.outputs:
238
+ results.append(
239
+ Response(
240
+ response_text=output.text,
241
+ response_length=len(output.token_ids),
242
+ prompt_length=len(final_output.prompt_token_ids),
243
+ finish_reason=output.finish_reason,
244
+ )
245
+ )
246
+
247
+ return results
248
+
249
+ @override
250
+ async def stream_chat(
251
+ self,
252
+ messages: list[dict[str, str]],
253
+ system: Optional[str] = None,
254
+ tools: Optional[str] = None,
255
+ images: Optional[list["ImageInput"]] = None,
256
+ videos: Optional[list["VideoInput"]] = None,
257
+ audios: Optional[list["AudioInput"]] = None,
258
+ **input_kwargs,
259
+ ) -> AsyncGenerator[str, None]:
260
+ generated_text = ""
261
+ generator = await self._generate(messages, system, tools, images, videos, audios, **input_kwargs)
262
+ async for result in generator:
263
+ delta_text = result.outputs[0].text[len(generated_text) :]
264
+ generated_text = result.outputs[0].text
265
+ yield delta_text
266
+
267
+ @override
268
+ async def get_scores(
269
+ self,
270
+ batch_input: list[str],
271
+ **input_kwargs,
272
+ ) -> list[float]:
273
+ raise NotImplementedError("vLLM engine does not support `get_scores`.")
src/llamafactory/cli.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ def main():
17
+ from .extras.misc import is_env_enabled
18
+
19
+ if is_env_enabled("USE_V1"):
20
+ from .v1 import launcher
21
+ else:
22
+ from . import launcher
23
+
24
+ launcher.launch()
25
+
26
+
27
+ if __name__ == "__main__":
28
+ from multiprocessing import freeze_support
29
+
30
+ freeze_support()
31
+ main()
src/llamafactory/data/__init__.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 the LlamaFactory team.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from .collator import (
16
+ KTODataCollatorWithPadding,
17
+ MultiModalDataCollatorForSeq2Seq,
18
+ PairwiseDataCollatorWithPadding,
19
+ SFTDataCollatorWith4DAttentionMask,
20
+ )
21
+ from .data_utils import Role, split_dataset
22
+ from .loader import get_dataset
23
+ from .template import TEMPLATES, Template, get_template_and_fix_tokenizer
24
+
25
+
26
+ __all__ = [
27
+ "TEMPLATES",
28
+ "KTODataCollatorWithPadding",
29
+ "MultiModalDataCollatorForSeq2Seq",
30
+ "PairwiseDataCollatorWithPadding",
31
+ "Role",
32
+ "SFTDataCollatorWith4DAttentionMask",
33
+ "Template",
34
+ "get_dataset",
35
+ "get_template_and_fix_tokenizer",
36
+ "split_dataset",
37
+ ]
src/llamafactory/data/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (640 Bytes). View file
 
src/llamafactory/data/__pycache__/collator.cpython-312.pyc ADDED
Binary file (28.6 kB). View file
 
src/llamafactory/data/__pycache__/converter.cpython-312.pyc ADDED
Binary file (21.7 kB). View file
 
src/llamafactory/data/__pycache__/data_utils.cpython-312.pyc ADDED
Binary file (8.7 kB). View file
 
src/llamafactory/data/__pycache__/formatter.cpython-312.pyc ADDED
Binary file (8.87 kB). View file
 
src/llamafactory/data/__pycache__/loader.cpython-312.pyc ADDED
Binary file (14.9 kB). View file
 
src/llamafactory/data/__pycache__/mm_plugin.cpython-312.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c41a6a5d07168e7ea8e94f99c087bbaf48c1ea00e8ad95c8e343884be0e4d3a5
3
+ size 125555
src/llamafactory/data/__pycache__/parser.cpython-312.pyc ADDED
Binary file (6.28 kB). View file