Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- README.md +966 -0
- requirements.txt +141 -0
- scripts/api_example/test_image.py +65 -0
- scripts/api_example/test_toolcall.py +77 -0
- scripts/bench_qwen.py +173 -0
- scripts/convert_ckpt/llamafy_baichuan2.py +112 -0
- scripts/convert_ckpt/llamafy_qwen.py +165 -0
- scripts/convert_ckpt/tiny_llama4.py +39 -0
- scripts/convert_ckpt/tiny_qwen3.py +32 -0
- scripts/dcp2hf.py +76 -0
- scripts/eval_bleu_rouge.py +79 -0
- scripts/hf2dcp.py +63 -0
- scripts/llama_pro.py +129 -0
- scripts/loftq_init.py +88 -0
- scripts/megatron_merge.py +130 -0
- scripts/pissa_init.py +86 -0
- scripts/qwen_omni_merge.py +140 -0
- scripts/stat_utils/cal_flops.py +49 -0
- scripts/stat_utils/cal_lr.py +98 -0
- scripts/stat_utils/cal_mfu.py +161 -0
- scripts/stat_utils/cal_ppl.py +134 -0
- scripts/stat_utils/length_cdf.py +69 -0
- scripts/vllm_infer.py +280 -0
- src/api.py +33 -0
- src/llamafactory/__init__.py +31 -0
- src/llamafactory/__pycache__/__init__.cpython-312.pyc +0 -0
- src/llamafactory/__pycache__/cli.cpython-312.pyc +0 -0
- src/llamafactory/__pycache__/launcher.cpython-312.pyc +0 -0
- src/llamafactory/api/__init__.py +0 -0
- src/llamafactory/api/app.py +133 -0
- src/llamafactory/api/chat.py +294 -0
- src/llamafactory/api/common.py +96 -0
- src/llamafactory/api/protocol.py +156 -0
- src/llamafactory/chat/__init__.py +19 -0
- src/llamafactory/chat/base_engine.py +98 -0
- src/llamafactory/chat/chat_model.py +200 -0
- src/llamafactory/chat/hf_engine.py +423 -0
- src/llamafactory/chat/sglang_engine.py +292 -0
- src/llamafactory/chat/vllm_engine.py +273 -0
- src/llamafactory/cli.py +31 -0
- src/llamafactory/data/__init__.py +37 -0
- src/llamafactory/data/__pycache__/__init__.cpython-312.pyc +0 -0
- src/llamafactory/data/__pycache__/collator.cpython-312.pyc +0 -0
- src/llamafactory/data/__pycache__/converter.cpython-312.pyc +0 -0
- src/llamafactory/data/__pycache__/data_utils.cpython-312.pyc +0 -0
- src/llamafactory/data/__pycache__/formatter.cpython-312.pyc +0 -0
- src/llamafactory/data/__pycache__/loader.cpython-312.pyc +0 -0
- src/llamafactory/data/__pycache__/mm_plugin.cpython-312.pyc +3 -0
- 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 |
+

|
| 2 |
+
|
| 3 |
+
[](https://github.com/hiyouga/LLaMA-Factory/stargazers)
|
| 4 |
+
[](https://github.com/hiyouga/LLaMA-Factory/commits/main)
|
| 5 |
+
[](https://github.com/hiyouga/LLaMA-Factory/graphs/contributors)
|
| 6 |
+
[](https://github.com/hiyouga/LLaMA-Factory/actions/workflows/tests.yml)
|
| 7 |
+
[](https://pypi.org/project/llamafactory/)
|
| 8 |
+
[](https://scholar.google.com/scholar?cites=12620864006390196564)
|
| 9 |
+
[](https://hub.docker.com/r/hiyouga/llamafactory/tags)
|
| 10 |
+
|
| 11 |
+
[](https://twitter.com/llamafactory_ai)
|
| 12 |
+
[](https://discord.gg/rKfvV9r9FK)
|
| 13 |
+
[](https://github.com/hiyouga/llamafactory-community)
|
| 14 |
+
[](https://blog.llamafactory.net/en/)
|
| 15 |
+
|
| 16 |
+
[](https://colab.research.google.com/drive/1eRTPn37ltBbYsISy9Aw2NuI2Aq5CQrD9?usp=sharing)
|
| 17 |
+
[](https://gallery.pai-ml.com/#/preview/deepLearning/nlp/llama_factory)
|
| 18 |
+
[](https://huggingface.co/spaces/hiyouga/LLaMA-Board)
|
| 19 |
+
[](https://modelscope.cn/studios/hiyouga/LLaMA-Board)
|
| 20 |
+
[](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 |
+

|
| 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 |
+

|
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
|
|
|