diff --git a/.github/scripts/check_doc_links.py b/.github/scripts/check_doc_links.py
new file mode 100644
index 000000000..d5f24c0d4
--- /dev/null
+++ b/.github/scripts/check_doc_links.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+"""Fail when a Sphinx warning reports a broken internal documentation link."""
+
+import argparse
+from pathlib import Path
+
+
+BLOCKING_WARNING_MARKERS = (
+ "[myst.xref_missing]",
+ "[myst.iref_missing]",
+ "[myst.xref_ambiguous]",
+ "[myst.iref_ambiguous]",
+ "[ref.",
+ "[toc.",
+)
+
+BLOCKING_WARNING_MESSAGES = (
+ "cross-reference target not found",
+ "reference target not found",
+ "undefined label:",
+ "failed to create a cross reference",
+ "toctree contains reference to",
+ "document isn't included in any toctree",
+ "duplicated entry found in toctree",
+)
+
+
+def find_blocking_warnings(warning_log: str) -> list[str]:
+ """Return warnings related to internal references and navigation."""
+ blocking_warnings = []
+ for line in warning_log.splitlines():
+ normalized_line = line.lower()
+ if any(marker in normalized_line for marker in BLOCKING_WARNING_MARKERS) or any(
+ message in normalized_line for message in BLOCKING_WARNING_MESSAGES
+ ):
+ blocking_warnings.append(line)
+ return blocking_warnings
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("warning_log", type=Path, help="Sphinx warning log generated with -w")
+ args = parser.parse_args()
+
+ blocking_warnings = find_blocking_warnings(args.warning_log.read_text(encoding="utf-8"))
+ if not blocking_warnings:
+ print("Documentation internal-link check passed.")
+ return 0
+
+ print("Documentation internal-link check failed:")
+ for warning in blocking_warnings:
+ print(warning)
+ return 1
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index a648d50ca..45922d131 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -54,6 +54,11 @@ jobs:
cache-dependency-path: |
requirements.txt
setup.py
+ - name: Install CPU-only PyTorch
+ run: >-
+ python -m pip install
+ --index-url https://download.pytorch.org/whl/cpu
+ torch==2.8.0+cpu
- name: Install LMFlow and test dependencies
run: python -m pip install -e ".[develop]"
- name: Run offline CPU tests
diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml
index 1ea066b15..831120e33 100644
--- a/.github/workflows/documentation.yaml
+++ b/.github/workflows/documentation.yaml
@@ -17,6 +17,7 @@ concurrency:
jobs:
build:
name: Build documentation
+ if: github.repository == 'OptimalScale/LMFlow'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
@@ -31,29 +32,44 @@ jobs:
- name: Install dependencies
run: python -m pip install -r docs/requirements.txt
- name: Build documentation
- run: sphinx-build -b html docs/source _build/html
- - name: Configure GitHub Pages
- if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
- uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
- - name: Upload Pages artifact
- if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
- uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
+ run: sphinx-build -b html -w _build/sphinx-warnings.log docs/source _build/html
+ - name: Check documentation links
+ run: python .github/scripts/check_doc_links.py _build/sphinx-warnings.log
+ - name: Upload documentation artifact
+ if: >-
+ github.repository == 'OptimalScale/LMFlow' &&
+ github.event_name != 'pull_request' &&
+ github.ref == 'refs/heads/main'
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
+ name: documentation-html
path: _build/html
+ if-no-files-found: error
+ retention-days: 1
deploy:
name: Deploy documentation
- if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
+ if: >-
+ github.repository == 'OptimalScale/LMFlow' &&
+ github.event_name != 'pull_request' &&
+ github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
- pages: write
- id-token: write
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
+ contents: write
steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
+ - name: Check out repository
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.0.2
+ - name: Download documentation artifact
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ with:
+ name: documentation-html
+ path: _build/html
+ - name: Deploy to gh-pages branch
+ uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_branch: gh-pages
+ publish_dir: _build/html
+ force_orphan: true
diff --git a/docs/requirements.txt b/docs/requirements.txt
index b17141911..d9f3a3077 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -4,4 +4,3 @@ sphinx_design
myst-parser
sphinx-autoapi
matplotlib
-numpydoc
\ No newline at end of file
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 6259349d2..4eeebdb95 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -37,7 +37,7 @@
"matplotlib.sphinxext.plot_directive",
# "myst_nb",
# "nbsphinx", # Uncomment and comment-out MyST-NB for local testing purposes.
- "numpydoc",
+ "sphinx.ext.napoleon",
# "sphinx_togglebutton",
# "sphinx_favicon",
]
@@ -46,6 +46,35 @@
autoapi_type = "python"
autoapi_dirs = ["../../src"]
+autoapi_options = [
+ "members",
+ "undoc-members",
+ "show-inheritance",
+ "show-module-summary",
+ "special-members",
+]
+
+myst_heading_anchors = 4
+show_warning_types = True
+
+_autoapi_internal_modules = (
+ "lmflow.pipeline.utils",
+ "lmflow.utils.deprecated",
+ "lmflow.utils.protocol",
+)
+
+
+def _skip_internal_autoapi_modules(app, what, name, obj, skip, options):
+ if what in {"module", "package"} and any(
+ name == module_name or name.startswith(f"{module_name}.")
+ for module_name in _autoapi_internal_modules
+ ):
+ return True
+ return skip
+
+
+def setup(app):
+ app.connect("autoapi-skip-member", _skip_internal_autoapi_modules)
source_suffix = {
".rst": "restructuredtext",
diff --git a/docs/source/examples/DATASETS.md b/docs/source/examples/DATASETS.md
index 220dcc7fc..daee2c420 100644
--- a/docs/source/examples/DATASETS.md
+++ b/docs/source/examples/DATASETS.md
@@ -149,12 +149,12 @@ Conversations should be formatted before feeding into the model. As of now, we'v
| Template Name | Filled Example | Detailed Template |
| ------------- | -------------- | ----------------- |
-| `chatglm3` | `[gMASK]sop<\|system\|>`
` You are a chatbot developed by LMFlow team.<\|user\|>`
` Who are you?<\|assistant\|>`
` I am a chatbot developed by LMFlow team.<\|user\|>`
` How old are you?<\|assistant\|>`
` I don't age like humans do. I exist as a piece of software, so I don't have a concept of age in the traditional sense.` | [Link](./supported_conversation_template.md#chatglm3) |
+| `chatglm3` | `[gMASK]sop<\|system\|>`
` You are a chatbot developed by LMFlow team.<\|user\|>`
` Who are you?<\|assistant\|>`
` I am a chatbot developed by LMFlow team.<\|user\|>`
` How old are you?<\|assistant\|>`
` I don't age like humans do. I exist as a piece of software, so I don't have a concept of age in the traditional sense.` | [Link](./supported_conversation_template.md#chatglm-3) |
| `chatml` | `<\|im_start\|>system`
`You are a chatbot developed by LMFlow team.<\|im_end\|>`
`<\|im_start\|>user`
`Who are you?<\|im_end\|>`
`<\|im_start\|>assistant`
`I am a chatbot developed by LMFlow team.<\|im_end\|>`
`<\|im_start\|>user`
`How old are you?<\|im_end\|>`
`<\|im_start\|>assistant`
`I don't age like humans do. I exist as a piece of software, so I don't have a concept of age in the traditional sense.<\|im_end\|>`
| [Link](./supported_conversation_template.md#chatml) |
-| `deepseek_v2` | `<|begin▁of▁sentence|>You are a chatbot developed by LMFlow team.`
`User: Who are you?`
`Assistant: I am a chatbot developed by LMFlow team.<|end▁of▁sentence|>User: How old are you?`
`Assistant: I don't age like humans do. I exist as a piece of software, so I don't have a concept of age in the traditional sense.<|end▁of▁sentence|>` | [Link](./supported_conversation_template.md#deepseek) |
+| `deepseek_v2` | `<|begin▁of▁sentence|>You are a chatbot developed by LMFlow team.`
`User: Who are you?`
`Assistant: I am a chatbot developed by LMFlow team.<|end▁of▁sentence|>User: How old are you?`
`Assistant: I don't age like humans do. I exist as a piece of software, so I don't have a concept of age in the traditional sense.<|end▁of▁sentence|>` | [Link](./supported_conversation_template.md#deepseek-v2) |
| `deepseek_v3` | -- | [Link](./supported_conversation_template.md#deepseek-v3) |
| `deepseek_r1` | -- | [Link](./supported_conversation_template.md#deepseek-r1-zero) |
-| `deepseek_r1_distill` | -- | [Link](./supported_conversation_template.md#deepseek-r1-distill-llamaqwenl) |
+| `deepseek_r1_distill` | -- | [Link](./supported_conversation_template.md#deepseek-r1-distill-llamaqwen) |
| `gemma` | `You are a chatbot developed by LMFlow team.user`
`Who are you?`
`model`
`I am a chatbot developed by LMFlow team.`
`user`
`How old are you?`
`model`
`I don't age like humans do. I exist as a piece of software, so I don't have a concept of age in the traditional sense.`
| [Link](./supported_conversation_template.md#gemma) |
| `hymba` | `System`
`You are a chatbot developed by LMFlow team.`
` {"name": "generate_qrcode", "description": "Generate a QR code for a given text", "parameters": {"type": "object", "properties": {"text": {"type": "string", "description": "The text to encode in the QR code"}}, "required": ["text"]}} `
`User`
`Who are you?`
`Assistant`
`I am a chatbot developed by LMFlow team.`
`User`
`How old are you?`
`Assistant`
`I don't age like humans do. I exist as a piece of software, so I don't have a concept of age in the traditional sense.` | [Link](./supported_conversation_template.md#hymba) |
| `internlm2` | `<\|im_start\|>system`
`You are a chatbot developed by LMFlow team.<\|im_end\|>`
`<\|im_start\|>user`
`Who are you?<\|im_end\|>`
`<\|im_start\|>assistant`
`I am a chatbot developed by LMFlow team.<\|im_end\|>`
`<\|im_start\|>user`
`How old are you?<\|im_end\|>`
`<\|im_start\|>assistant`
`I don't age like humans do. I exist as a piece of software, so I don't have a concept of age in the traditional sense.<\|im_end\|>`
| [Link](./supported_conversation_template.md#internlm2) |
@@ -389,4 +389,4 @@ please refer to [conversation data](#conversation).
]
}
```
-````
\ No newline at end of file
+````
diff --git a/docs/source/examples/customize_conversation_template.md b/docs/source/examples/customize_conversation_template.md
index 0b58b3347..1738a9afe 100644
--- a/docs/source/examples/customize_conversation_template.md
+++ b/docs/source/examples/customize_conversation_template.md
@@ -5,7 +5,7 @@
We provide the flexibility to customize the conversation template. You can customize your own conversation template by following the steps below:
-### 1. Decompose your conversations
+## 1. Decompose your conversations
Say you want to make the conversations between user and assistant look like:
```
@@ -32,7 +32,7 @@ It is easy to abstract the format for each message:
Also, we have a bos token at the beginning of the conversation session.
-### 2. Choose proper `Formatter`
+## 2. Choose proper `Formatter`
Recall the requirements for a conversation dataset:
> - `system`: `Optional[string]`.
> - `tools`: `Optional[list[string]]`.
@@ -42,7 +42,7 @@ Recall the requirements for a conversation dataset:
System message, user message, and assistant message are strings thus we can use `StringFormatter` for them.
-### 3. Build the template
+## 3. Build the template
All preset templates are located at `src/lmflow/utils/conversation_template`.
Within the template file, define your own template like:
@@ -89,7 +89,7 @@ YOUR_TEMPLATE = ConversationTemplate(
Feel free to create your own template by inheriting the `ConversationTemplate` class. Llama-2 v.s. llama-3 would be a good examples to refer to.
-### 4. Register your template
+## 4. Register your template
After defining your own template, you need to register it in the `src/lmflow/utils/conversation_template/__init__.py` file.
```python
@@ -103,7 +103,7 @@ PRESET_TEMPLATES = {
}
```
-### 5. Use your template
+## 5. Use your template
You are all set! Specify the template name in, for example, your finetune script:
```bash
@@ -112,4 +112,4 @@ You are all set! Specify the template name in, for example, your finetune script
--dataset_path your_conversation_dataset \
--conversation_template your_template_name \
--output_model_path output_models/your_model
-```
\ No newline at end of file
+```
diff --git a/docs/source/examples/index.md b/docs/source/examples/index.md
index 30dc1d335..05a8619aa 100644
--- a/docs/source/examples/index.md
+++ b/docs/source/examples/index.md
@@ -8,6 +8,8 @@ We provide several examples to show how to use our package in your problem.
:maxdepth: 3
DATASETS
+supported_conversation_template
+customize_conversation_template
```
```{toctree}
@@ -24,6 +26,7 @@ For SFT,
:maxdepth: 3
finetuning
+medical_finetune
```
@@ -54,4 +57,3 @@ Refer to [examples](https://github.com/OptimalScale/LMFlow/blob/main/examples).
TASK_GUIDE
```
-
diff --git a/src/lmflow/args.py b/src/lmflow/args.py
index 249c29486..9841d883b 100644
--- a/src/lmflow/args.py
+++ b/src/lmflow/args.py
@@ -855,6 +855,8 @@ class InferencerArguments:
Define a class InferencerArguments using the dataclass decorator. The class contains several optional
parameters that can be used to configure a inferencer.
+ Parameters
+ ----------
local_rank : str
For distributed training: local_rank
random_seed : int, default = 1
diff --git a/src/lmflow/datasets/dataset.py b/src/lmflow/datasets/dataset.py
index 014e52f4a..ab8d58b81 100644
--- a/src/lmflow/datasets/dataset.py
+++ b/src/lmflow/datasets/dataset.py
@@ -148,21 +148,21 @@ def _check_hf_json_format(self, data_files: list[str]):
)
def from_dict(self, dict_obj: dict, *args, **kwargs):
- r"""
- Create a Dataset object from a dictionary.
+ """Populate this dataset from an LMFlow dataset dictionary.
+
+ The expected dictionary shape is::
- Return a Dataset given a dict with format:
{
"type": TYPE,
"instances": [
{
- "key_1": VALUE_1.1,
- "key_2": VALUE_1.2,
+ "key_1": VALUE_1_1,
+ "key_2": VALUE_1_2,
...
},
{
- "key_1": VALUE_2.1,
- "key_2": VALUE_2.2,
+ "key_1": VALUE_2_1,
+ "key_2": VALUE_2_2,
...
},
...
@@ -170,21 +170,18 @@ def from_dict(self, dict_obj: dict, *args, **kwargs):
}
Parameters
- -----------
-
- dict_obj : dict.
- A dictionary containing the dataset information.
-
- args : Optional.
- Positional arguments.
-
- kwargs : Optional.
- Keyword arguments.
+ ----------
+ dict_obj : dict
+ Dataset data containing ``type`` and ``instances`` keys.
+ *args
+ Positional arguments passed to the selected dataset backend.
+ **kwargs
+ Keyword arguments passed to the selected dataset backend.
Returns
- ---------
-
- self : Dataset object.
+ -------
+ Dataset
+ This dataset instance.
"""
if self.backend == "huggingface":
if KEY_TYPE not in dict_obj:
@@ -247,29 +244,31 @@ def create_from_dict(cls, dict_obj, *args, **kwargs):
return dataset.from_dict(dict_obj)
def to_dict(self):
- r"""
- Returns
- ---------
+ """Convert this dataset to the LMFlow dictionary format.
+
+ The returned dictionary has the following shape::
- Return a dict represents the dataset:
{
"type": TYPE,
"instances": [
{
- "key_1": VALUE_1.1,
- "key_2": VALUE_1.2,
+ "key_1": VALUE_1_1,
+ "key_2": VALUE_1_2,
...
},
{
- "key_1": VALUE_2.1,
- "key_2": VALUE_2.2,
+ "key_1": VALUE_2_1,
+ "key_2": VALUE_2_2,
...
},
...
]
}
- A python dict object represents the content of this dataset.
+ Returns
+ -------
+ dict
+ Dataset data containing ``type`` and ``instances`` keys.
"""
if self.backend == "huggingface":
dict_obj = {}
diff --git a/src/lmflow/datasets/multi_modal_dataset.py b/src/lmflow/datasets/multi_modal_dataset.py
index 053ec957c..4c51c71f7 100644
--- a/src/lmflow/datasets/multi_modal_dataset.py
+++ b/src/lmflow/datasets/multi_modal_dataset.py
@@ -136,15 +136,21 @@ def insert_separator(X, sep):
def preprocess_llama_from_llava_plain(sources, tokenizer: transformers.PreTrainedTokenizer, has_image: bool = False):
- """
- This function just add the image in the front of text.
- And don't add any prompt.
- Args:
- sources: The input data with text and image.
- tokenizer: The tokenizer to process text.
- has_image: Whether the input data has image.
- Returns:
- The input_ids and labels for the model.
+ """Preprocess plain LLaVA samples without adding a prompt.
+
+ Parameters
+ ----------
+ sources
+ Input samples containing text and image placeholders.
+ tokenizer : transformers.PreTrainedTokenizer
+ Tokenizer used to encode text.
+ has_image : bool
+ Whether the samples contain images.
+
+ Returns
+ -------
+ dict
+ Model ``input_ids`` and ``labels``.
"""
conversations = []
for source in sources:
@@ -164,15 +170,21 @@ def preprocess_llama_from_llava_plain(sources, tokenizer: transformers.PreTraine
def preprocess_llama_from_llava_v1(sources, tokenizer: transformers.PreTrainedTokenizer, has_image: bool = False):
- """
- This function add the prompt and then put the image after the prompt.
- So it needs additional code to generate the target label.
- Args:
- sources: The input data with text and image.
- tokenizer: The tokenizer to process text.
- has_image: Whether the input data has image.
- Returns:
- The input_ids and labels for the model.
+ """Preprocess LLaVA v1 samples and generate target labels.
+
+ Parameters
+ ----------
+ sources
+ Input samples containing text and image placeholders.
+ tokenizer : transformers.PreTrainedTokenizer
+ Tokenizer used to encode text.
+ has_image : bool
+ Whether the samples contain images.
+
+ Returns
+ -------
+ dict
+ Model ``input_ids`` and ``labels``.
"""
conv = conversation_lib.default_conversation.copy()
diff --git a/src/lmflow/models/hf_decoder_model.py b/src/lmflow/models/hf_decoder_model.py
index 66b57cccc..dc00ef903 100644
--- a/src/lmflow/models/hf_decoder_model.py
+++ b/src/lmflow/models/hf_decoder_model.py
@@ -232,30 +232,33 @@ def tokenize(self, dataset: Dataset, add_special_tokens=True, *args, **kwargs) -
return tokenized_datasets
def encode(self, input: Union[str, list[str]], *args, **kwargs) -> Union[list[int], list[list[int]]]:
- """
- Perform encoding process of the tokenizer.
+ """Encode one string or a batch of strings with the model tokenizer.
Parameters
- ------------
- inputs : str or list.
- The text sequence.
+ ----------
+ input : str or list[str]
+ Text input to encode.
+ *args
+ Positional tokenizer arguments.
+ **kwargs
+ Keyword tokenizer arguments.
- args : Optional.
- Positional arguments.
+ Returns
+ -------
+ transformers.BatchEncoding or list[int]
+ A batch encoding for list input, or token ids for string input.
- kwargs : Optional.
- Keyword arguments.
+ Examples
+ --------
+ A single string returns token IDs::
+
+ "Hello, world!" -> [101, 7592, 1010, 2088, 102]
+
+ A list of strings returns a batch encoding with fields such as::
- Returns
- ------------
- outputs :
- if string input,return the tokenized inputs.
- "Hello,world!"-> [101, 7592, 1010, 2088, 102]
- if batch input,return {input_ids,attention_mask,token_type_ids}
- ["Hello,world!","Hello!"] ->
{
- 'input_ids': tensor([[ 101, 7592, 1010, 2088, 102],...),
- 'attention_mask': tensor([[1, 1, 1, 1, 1],[0,0,1,1,1]])
+ "input_ids": tensor([[101, 7592, 1010, 2088, 102], ...]),
+ "attention_mask": tensor([[1, 1, 1, 1, 1], [0, 0, 1, 1, 1]])
}
"""
if isinstance(input, list):
diff --git a/src/lmflow/models/vision2seq_model.py b/src/lmflow/models/vision2seq_model.py
index b80982df7..cfd92e9a8 100644
--- a/src/lmflow/models/vision2seq_model.py
+++ b/src/lmflow/models/vision2seq_model.py
@@ -174,6 +174,7 @@ def save_prompt_cache(self, path):
def load_prompt_cache(self, path):
"""
Load prompt embedding and id.
+
Args:
path: The path to load the prompt embedding and id.
@@ -202,6 +203,7 @@ def forward(
image_token_indexes: Optional[list] = None,
one_sample_multiple_images: bool = False,
) -> Union[tuple, CausalLMOutputWithPast]:
+ """Run a multimodal forward pass through the vision and language models."""
if not image_token_indexes:
image_token_indexes = [0]
diff --git a/src/lmflow/optim/adabelief.py b/src/lmflow/optim/adabelief.py
index 717fdd428..a39abdc48 100644
--- a/src/lmflow/optim/adabelief.py
+++ b/src/lmflow/optim/adabelief.py
@@ -87,9 +87,11 @@ def reset(self):
def step(self, closure=None):
"""Performs a single optimization step.
- Arguments:
- closure (callable, optional): A closure that reevaluates the model
- and returns the loss.
+
+ Parameters
+ ----------
+ closure : callable, optional
+ A closure that reevaluates the model and returns the loss.
"""
loss = None
if closure is not None:
diff --git a/src/lmflow/optim/adabound.py b/src/lmflow/optim/adabound.py
index e30511967..3de6909c1 100644
--- a/src/lmflow/optim/adabound.py
+++ b/src/lmflow/optim/adabound.py
@@ -7,13 +7,12 @@
class AdaBound(Optimizer):
- r"""Implements AdaBound algorithm.
+ """Implement the AdaBound optimization algorithm.
- It has been proposed in `Adaptive Gradient Methods with Dynamic Bound of
- Learning Rate
- https://arxiv.org/abs/1902.09843
- Note:
- Reference code: https://github.com/Luolc/AdaBound
+ See "Adaptive Gradient Methods with Dynamic Bound of Learning Rate":
+ https://arxiv.org/abs/1902.09843.
+
+ Reference implementation: https://github.com/Luolc/AdaBound.
"""
def __init__(
diff --git a/src/lmflow/optim/lars.py b/src/lmflow/optim/lars.py
index e507f96fc..3256366c0 100644
--- a/src/lmflow/optim/lars.py
+++ b/src/lmflow/optim/lars.py
@@ -7,6 +7,7 @@
class LARS(Optimizer):
r"""Extends SGD in PyTorch with LARS scaling from the paper
`Large batch training of Convolutional Networks`__.
+
.. note::
The application of momentum in the SGD part is modified according to
the PyTorch standards. LARS scaling fits into the equation in the
diff --git a/src/lmflow/optim/radam.py b/src/lmflow/optim/radam.py
index 39d2f590e..a0d590b78 100644
--- a/src/lmflow/optim/radam.py
+++ b/src/lmflow/optim/radam.py
@@ -11,7 +11,7 @@ class RAdam(Optimizer):
r"""Implements RAdam optimization algorithm.
Note:
- Deprecated, please use version provided by PyTorch_.
+ Deprecated; use the version provided by ``torch.optim``.
It has been proposed in `On the Variance of the Adaptive Learning
Rate and Beyond`.
diff --git a/src/lmflow/optim/utils.py b/src/lmflow/optim/utils.py
index 57430bdd0..b0a46269e 100644
--- a/src/lmflow/optim/utils.py
+++ b/src/lmflow/optim/utils.py
@@ -1,10 +1,10 @@
from typing import Any, Optional
-from transformers import PreTrainedModel
+from transformers import PreTrainedModel, TrainingArguments
from transformers.utils import is_sagemaker_mp_enabled
import lmflow.optim.optimizers as optim
-from lmflow.args import OptimizerNames, TrainingArguments
+from lmflow.args import OptimizerNames
def create_customized_optimizer(base_trainer_class, model_args):
diff --git a/src/lmflow/pipeline/dpo_aligner.py b/src/lmflow/pipeline/dpo_aligner.py
index 254762e46..b206d1f15 100644
--- a/src/lmflow/pipeline/dpo_aligner.py
+++ b/src/lmflow/pipeline/dpo_aligner.py
@@ -29,15 +29,17 @@ def get_paired_dataset(
) -> Dataset:
"""Load dataset and convert it to the necessary format.
- The dataset is converted to a dictionary with the following structure:
- {
- 'prompt': list[str],
- 'chosen': list[str],
- 'rejected': list[str],
- }
-
- Prompts are structured as follows:
- "Question: " + + "\n\nAnswer: "
+ The returned dataset uses the following structure::
+
+ {
+ "prompt": list[str],
+ "chosen": list[str],
+ "rejected": list[str]
+ }
+
+ Each prompt is formatted as::
+
+ "Question: " + prompt + "\n\nAnswer: "
"""
data_path = Path(data_root) / data_dir
data_files = [x.absolute().as_posix() for x in data_path.glob("*.json")]
diff --git a/src/lmflow/utils/conversation_template/base.py b/src/lmflow/utils/conversation_template/base.py
index ceafac708..9d1488e79 100644
--- a/src/lmflow/utils/conversation_template/base.py
+++ b/src/lmflow/utils/conversation_template/base.py
@@ -21,28 +21,26 @@ class TemplateComponent:
Parameters
----------
type : Literal['token', 'token_id', 'string', 'tools']
- - Type of the component.
+ Type of the component.
- - When the component is a token or a string, the content should be `string`.
+ When the component is a token or a string, the content should be `string`.
The difference between the two is that token will be converted to token ids
by the tokenizer.convert_tokens_to_ids() method, while string will be directly
encoded by the tokenizer.encode() method. Specially, since the bos token and eos
token are frequently used across different templates, we provide the convenience
to use `'bos_token'` and `'eos_token'` to represent the actual bos and eos tokens when
- `type` of the `TemplateComponent` is `token`. For example:
+ `type` of the `TemplateComponent` is `token`. For example::
- ```python
- TemplateComponent(type='token', content='bos_token')
- ```
+ TemplateComponent(type='token', content='bos_token')
After encoding, the content will be replaced by the actual token id of the bos token.
Please do remember that if you set the `type` to `string`, the tokenizer will try to
encode the string 'bos_token' instead of providing the actual bos token.
- - When the component is token_id, the content should be `int` or `list[int]`, and
+ When the component is token_id, the content should be `int` or `list[int]`, and
will be directly appended to the encoded token ids.
- - Tools are not supported yet.
+ Tools are not supported yet.
content : Union[str, int, list[str], list[int]]
Content of the component.
@@ -189,24 +187,23 @@ def encode_conversation(
) -> Sequence[tuple[list[int], list[int]]]:
r"""
Messages here should be guaranteed to be in pairs, with the first message being the user message and the second message being the system message.
- Data example:
- ```json
- {
- "conversation_id": 2,
- "system": "sysinfo1",
- "tools": ["tool_1_desc"],
- "messages": [
- {
- "role": "user",
- "content": "hi"
- },
- {
- "role": "assistant",
- "content": "Hello!"
- }
- ]
- }
- ```
+ Data example::
+
+ {
+ "conversation_id": 2,
+ "system": "sysinfo1",
+ "tools": ["tool_1_desc"],
+ "messages": [
+ {
+ "role": "user",
+ "content": "hi"
+ },
+ {
+ "role": "assistant",
+ "content": "Hello!"
+ }
+ ]
+ }
"""
assert isinstance(messages, list), "Messages must be a list."
@@ -414,24 +411,23 @@ def encode_conversation(
) -> Sequence[tuple[list[int], list[int]]]:
r"""
Messages here should be guaranteed to be in pairs, with the first message being the user message and the second message being the system message.
- Data example:
- ```json
- {
- "conversation_id": 2,
- "system": "sysinfo1",
- "tools": ["tool_1_desc"],
- "messages": [
- {
- "role": "user",
- "content": "hi"
- },
- {
- "role": "assistant",
- "content": "Hello!"
- }
- ]
- }
- ```
+ Data example::
+
+ {
+ "conversation_id": 2,
+ "system": "sysinfo1",
+ "tools": ["tool_1_desc"],
+ "messages": [
+ {
+ "role": "user",
+ "content": "hi"
+ },
+ {
+ "role": "assistant",
+ "content": "Hello!"
+ }
+ ]
+ }
"""
assert isinstance(messages, list), "Messages must be a list."
diff --git a/src/lmflow/utils/conversation_template/gemma.py b/src/lmflow/utils/conversation_template/gemma.py
index 504923c8e..d73f8f884 100644
--- a/src/lmflow/utils/conversation_template/gemma.py
+++ b/src/lmflow/utils/conversation_template/gemma.py
@@ -11,6 +11,7 @@
@dataclass
class GemmaConversationTemplate(ConversationTemplate):
def encode_conversation(self, *args, **kwargs):
+ """Encode a Gemma conversation and handle optional system prompts."""
if kwargs.get("system"):
logger.warning(
"As of now, Gemma does not support system messages officially. "
diff --git a/src/lmflow/utils/data_utils.py b/src/lmflow/utils/data_utils.py
index 7ce34772a..50b469e9e 100644
--- a/src/lmflow/utils/data_utils.py
+++ b/src/lmflow/utils/data_utils.py
@@ -17,7 +17,7 @@ def set_random_seed(seed: int):
Set the random seed for `random`, `numpy`, `torch`, `torch.cuda`.
Parameters
- ------------
+ ----------
seed : int
The default seed.
@@ -34,17 +34,17 @@ def load_data(file_name: str):
Load data with file name.
Parameters
- ------------
- file_name : str.
+ ----------
+ file_name : str
The dataset file name.
Returns
- ------------
- inputs : list.
+ -------
+ inputs : list
The input texts of the dataset.
- outputs : list.
+ outputs : list
The output texts file datasets.
- len : int.
+ length : int
The length of the dataset.
"""
inputs = []
@@ -68,17 +68,17 @@ def batchlize(examples: list, batch_size: int, random_shuffle: bool):
Convert examples to a dataloader.
Parameters
- ------------
- examples : list.
+ ----------
+ examples : list
Data list.
- batch_size : int.
-
+ batch_size : int
+ Number of examples in each batch.
random_shuffle : bool
If true, the dataloader shuffle the training data.
Returns
- ------------
- dataloader:
+ -------
+ list
Dataloader with batch generator.
"""
size = 0
@@ -152,22 +152,21 @@ def check_dataset_instances_key_fast(file_path: str, instances_key: str, max_lin
return False
-def answer_extraction(response, answer_type=None): # use this funtion to extract answers from generated text
+def answer_extraction(response, answer_type=None): # use this function to extract answers from generated text
"""
- Use this funtion to extract answers from generated text
+ Extract answers from generated text.
Parameters
- ------------
- args :
- Arguments.
+ ----------
response : str
plain string response.
-
+ answer_type : str, optional
+ Type of answer to extract.
Returns
- ------------
- answer:
- Decoded answer (such as A, B, C, D, E for mutiple-choice QA).
+ -------
+ str
+ Decoded answer (such as A, B, C, D, E for multiple-choice QA).
"""
# temp = response["generated_text"]
@@ -286,10 +285,14 @@ def process_image_flag(text, image_flag=""):
class VLLMInferenceResultWithInput(TypedDict):
+ """Structured vLLM inference result with its original input."""
+
input: str
output: Union[list[str], list[list[int]]]
class RewardModelInferenceResultWithInput(TypedDict):
+ """Structured reward-model inference result with its original input."""
+
input: str
output: list[dict[str, Union[str, float]]] # [{"score": 0.5, "text": "output text"}]