This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
def test_llm():
|
||||
from swift import AppArguments, app_main
|
||||
app_main(AppArguments(model='Qwen/Qwen2.5-0.5B-Instruct'))
|
||||
|
||||
|
||||
def test_lora():
|
||||
from swift import AppArguments, app_main
|
||||
app_main(AppArguments(adapters='swift/test_lora', lang='en', studio_title='小黄'))
|
||||
|
||||
|
||||
def test_mllm():
|
||||
from swift import AppArguments, app_main
|
||||
app_main(AppArguments(model='Qwen/Qwen2-VL-7B-Instruct', stream=True))
|
||||
|
||||
|
||||
def test_audio():
|
||||
from swift import AppArguments, DeployArguments, app_main, run_deploy
|
||||
deploy_args = DeployArguments(model='Qwen/Qwen2-Audio-7B-Instruct', infer_backend='transformers', verbose=False)
|
||||
|
||||
with run_deploy(deploy_args, return_url=True) as url:
|
||||
app_main(AppArguments(model='Qwen2-Audio-7B-Instruct', base_url=url, stream=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_mllm()
|
||||
@@ -0,0 +1,63 @@
|
||||
def _test_client(port=8000):
|
||||
import time
|
||||
|
||||
from swift.dataset import load_dataset
|
||||
from swift.infer_engine import InferClient, InferRequest, RequestConfig
|
||||
dataset = load_dataset(['AI-ModelScope/alpaca-gpt4-data-zh#1000'], num_proc=4)
|
||||
infer_client = InferClient(port=port)
|
||||
while True:
|
||||
try:
|
||||
infer_client.models
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(1)
|
||||
pass
|
||||
infer_requests = []
|
||||
for data in dataset[0]:
|
||||
infer_requests.append(InferRequest(**data))
|
||||
request_config = RequestConfig(seed=42, max_tokens=256, temperature=0.8)
|
||||
|
||||
resp = infer_client.infer(infer_requests, request_config=request_config, use_tqdm=False)
|
||||
print(len(resp))
|
||||
|
||||
|
||||
def _test(infer_backend):
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
from swift.arguments import DeployArguments
|
||||
from swift.pipelines import run_deploy
|
||||
args = DeployArguments(model='Qwen/Qwen2-7B-Instruct', infer_backend=infer_backend, verbose=False)
|
||||
with run_deploy(args) as port:
|
||||
_test_client(port)
|
||||
|
||||
|
||||
def test_vllm():
|
||||
_test('vllm')
|
||||
|
||||
|
||||
def test_lmdeploy():
|
||||
_test('lmdeploy')
|
||||
|
||||
|
||||
def test_pt():
|
||||
_test('transformers')
|
||||
|
||||
|
||||
def test_vllm_origin():
|
||||
import subprocess
|
||||
import sys
|
||||
from modelscope import snapshot_download
|
||||
model_dir = snapshot_download('Qwen/Qwen2-7B-Instruct')
|
||||
args = [sys.executable, '-m', 'vllm.entrypoints.openai.api_server', '--model', model_dir]
|
||||
process = subprocess.Popen(args)
|
||||
_test_client()
|
||||
process.terminate()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_vllm_origin()
|
||||
# test_vllm()
|
||||
test_lmdeploy()
|
||||
# test_pt()
|
||||
@@ -0,0 +1,116 @@
|
||||
def _test_client(port: int, print_logprobs: bool = False, test_vlm: bool = False):
|
||||
import aiohttp
|
||||
import time
|
||||
from pprint import pprint
|
||||
|
||||
from swift.infer_engine import InferClient, InferRequest, RequestConfig
|
||||
|
||||
infer_client = InferClient(port=port)
|
||||
|
||||
while True:
|
||||
try:
|
||||
models = infer_client.models
|
||||
print(f'models: {models}')
|
||||
except aiohttp.ClientConnectorError:
|
||||
time.sleep(5)
|
||||
continue
|
||||
break
|
||||
|
||||
if test_vlm:
|
||||
query = '这是什么'
|
||||
# http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png
|
||||
messages = [{
|
||||
'role':
|
||||
'user',
|
||||
'content': [
|
||||
{
|
||||
'type': 'text',
|
||||
'text': '这是什么'
|
||||
},
|
||||
{
|
||||
'type': 'image_url',
|
||||
'image_url': {
|
||||
'url': 'cat.png'
|
||||
}
|
||||
},
|
||||
]
|
||||
}]
|
||||
else:
|
||||
query = '123*234=?'
|
||||
messages = [{'role': 'user', 'content': query}]
|
||||
|
||||
infer_request = InferRequest(messages=messages)
|
||||
request_config = RequestConfig(seed=42, max_tokens=256, temperature=0.8, logprobs=True, top_logprobs=5)
|
||||
|
||||
resp = infer_client.infer([infer_request], request_config=request_config)[0]
|
||||
response = resp.choices[0].message.content
|
||||
print(f'query: {query}')
|
||||
print(f'response: {response}')
|
||||
if print_logprobs:
|
||||
pprint(resp.choices[0].logprobs)
|
||||
|
||||
request_config = RequestConfig(
|
||||
stream=True, seed=42, max_tokens=256, temperature=0.8, top_k=20, top_p=0.8, logprobs=True, top_logprobs=5)
|
||||
gen_list = infer_client.infer([infer_request], request_config=request_config)
|
||||
print(f'query: {query}')
|
||||
print('response: ', end='')
|
||||
for chunk in gen_list[0]:
|
||||
if chunk is None:
|
||||
continue
|
||||
print(chunk.choices[0].delta.content, end='', flush=True)
|
||||
if print_logprobs and chunk.choices[0].logprobs is not None:
|
||||
pprint(chunk.choices[0].logprobs)
|
||||
print()
|
||||
|
||||
|
||||
def _test(infer_backend, test_vlm: bool = False):
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
import multiprocessing
|
||||
|
||||
from swift import DeployArguments, deploy_main
|
||||
mp = multiprocessing.get_context('spawn')
|
||||
model = 'Qwen/Qwen2-VL-7B-Instruct' if test_vlm else 'Qwen/Qwen2-7B-Instruct'
|
||||
args = DeployArguments(model=model, infer_backend=infer_backend, verbose=False)
|
||||
process = mp.Process(target=deploy_main, args=(args, ))
|
||||
process.start()
|
||||
_test_client(args.port, True, test_vlm)
|
||||
process.terminate()
|
||||
|
||||
|
||||
def test_vllm_vlm():
|
||||
_test('vllm', test_vlm=True)
|
||||
|
||||
|
||||
def test_vllm():
|
||||
_test('vllm')
|
||||
|
||||
|
||||
def test_lmdeploy():
|
||||
_test('lmdeploy')
|
||||
|
||||
|
||||
def test_pt():
|
||||
_test('transformers')
|
||||
|
||||
|
||||
def test_vllm_origin():
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from modelscope import snapshot_download
|
||||
model_dir = snapshot_download('Qwen/Qwen2-7B-Instruct')
|
||||
args = [sys.executable, '-m', 'vllm.entrypoints.openai.api_server', '--model', model_dir]
|
||||
process = subprocess.Popen(args)
|
||||
_test_client(8000)
|
||||
process.terminate()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_vllm_origin()
|
||||
# test_vllm()
|
||||
test_vllm_vlm()
|
||||
# test_lmdeploy()
|
||||
# test_pt()
|
||||
@@ -0,0 +1,64 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
infer_backend = 'transformers'
|
||||
|
||||
|
||||
def test_eval_native():
|
||||
from swift import EvalArguments, eval_main
|
||||
eval_main(
|
||||
EvalArguments(
|
||||
model='Qwen/Qwen2.5-0.5B-Instruct',
|
||||
eval_dataset='arc',
|
||||
infer_backend=infer_backend,
|
||||
eval_backend='Native',
|
||||
eval_limit=10,
|
||||
eval_generation_config={
|
||||
'max_new_tokens': 128,
|
||||
'temperature': 0.1
|
||||
},
|
||||
extra_eval_args={'ignore_errors': False},
|
||||
))
|
||||
|
||||
|
||||
def test_eval_llm():
|
||||
from swift import EvalArguments, eval_main
|
||||
eval_main(
|
||||
EvalArguments(
|
||||
model='Qwen/Qwen2.5-0.5B-Instruct',
|
||||
eval_dataset='arc_c',
|
||||
infer_backend=infer_backend,
|
||||
eval_backend='OpenCompass',
|
||||
eval_limit=10))
|
||||
|
||||
|
||||
def test_eval_mllm():
|
||||
from swift import EvalArguments, eval_main
|
||||
eval_main(
|
||||
EvalArguments(
|
||||
model='Qwen/Qwen2.5-VL-3B-Instruct',
|
||||
eval_dataset=['realWorldQA'],
|
||||
infer_backend='transformers',
|
||||
eval_backend='VLMEvalKit',
|
||||
eval_limit=10,
|
||||
eval_generation_config={
|
||||
'max_new_tokens': 128,
|
||||
'temperature': 0.1
|
||||
}))
|
||||
|
||||
|
||||
def test_eval_url():
|
||||
from swift import DeployArguments, EvalArguments, eval_main
|
||||
from swift.pipelines import run_deploy
|
||||
deploy_args = DeployArguments(model='Qwen/Qwen2-VL-7B-Instruct', infer_backend=infer_backend, verbose=False)
|
||||
|
||||
with run_deploy(deploy_args, return_url=True) as url:
|
||||
eval_main(EvalArguments(model='Qwen2-VL-7B-Instruct', eval_url=url, eval_dataset=['arc']))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_eval_llm()
|
||||
# test_eval_mllm()
|
||||
# test_eval_url()
|
||||
# test_eval_native()
|
||||
@@ -0,0 +1,104 @@
|
||||
import os
|
||||
from typing import Literal
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def test_llm_quant(quant_method: Literal['gptq', 'awq'] = 'awq'):
|
||||
from swift import ExportArguments, export_main
|
||||
export_main(
|
||||
ExportArguments(
|
||||
model='Qwen/Qwen2-7B-Instruct',
|
||||
quant_bits=4,
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#1000', 'AI-ModelScope/alpaca-gpt4-data-en#1000'],
|
||||
quant_method=quant_method))
|
||||
|
||||
|
||||
def test_vlm_quant(quant_method: Literal['gptq', 'awq'] = 'awq'):
|
||||
from swift import ExportArguments, export_main
|
||||
export_main(
|
||||
ExportArguments(
|
||||
model='Qwen/Qwen2-VL-7B-Instruct',
|
||||
quant_bits=4,
|
||||
dataset=['modelscope/coco_2014_caption:validation#1000'],
|
||||
quant_method=quant_method))
|
||||
|
||||
|
||||
def test_audio_quant(quant_method: Literal['gptq', 'awq'] = 'awq'):
|
||||
from swift import ExportArguments, export_main
|
||||
export_main(
|
||||
ExportArguments(
|
||||
model='Qwen/Qwen2-Audio-7B-Instruct',
|
||||
quant_bits=4,
|
||||
dataset=['speech_asr/speech_asr_aishell1_trainsets:validation#1000'],
|
||||
quant_method=quant_method))
|
||||
|
||||
|
||||
def test_vlm_bnb_quant():
|
||||
from swift import ExportArguments, InferArguments, export_main, infer_main
|
||||
export_main(ExportArguments(model='Qwen/Qwen2-VL-7B-Instruct', quant_bits=4, quant_method='bnb'))
|
||||
|
||||
# infer_main(InferArguments(ckpt_dir='Qwen/Qwen2-VL-7B-Instruct-bnb-int4'))
|
||||
|
||||
|
||||
def test_bert():
|
||||
from swift import ExportArguments, export_main
|
||||
output_dir = 'output/swift_test_bert_merged'
|
||||
export_main(ExportArguments(adapters='swift/test_bert', merge_lora=True, output_dir=output_dir))
|
||||
export_main(
|
||||
ExportArguments(model=output_dir, load_data_args=True, quant_bits=4, quant_method='gptq', max_length=512))
|
||||
|
||||
|
||||
def test_reward_model():
|
||||
from swift import ExportArguments, export_main
|
||||
|
||||
export_main(
|
||||
ExportArguments(
|
||||
model='Shanghai_AI_Laboratory/internlm2-1_8b-reward',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#1000', 'AI-ModelScope/alpaca-gpt4-data-en#1000'],
|
||||
quant_bits=4,
|
||||
quant_method='gptq'))
|
||||
|
||||
|
||||
def test_fp8():
|
||||
from swift import ExportArguments, InferArguments, export_main, infer_main
|
||||
export_main(ExportArguments(model='Qwen/Qwen2.5-3B-Instruct', quant_method='fp8'))
|
||||
infer_main(InferArguments(model='Qwen2.5-3B-Instruct-fp8'))
|
||||
|
||||
|
||||
def test_lora_merge_export_minimal():
|
||||
from swift import ExportArguments, InferArguments, SftArguments, export_main, infer_main, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2-0.5B',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#20'],
|
||||
max_steps=2,
|
||||
per_device_train_batch_size=1,
|
||||
gradient_accumulation_steps=1,
|
||||
save_steps=2,
|
||||
split_dataset_ratio=0.01,
|
||||
tuner_type='lora',
|
||||
logging_steps=1,
|
||||
output_dir='output/test_lora_merge_export'))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
merge_output_dir = 'output/test_lora_merge_export_merged'
|
||||
export_main(
|
||||
ExportArguments(
|
||||
adapters=last_model_checkpoint,
|
||||
merge_lora=True,
|
||||
output_dir=merge_output_dir,
|
||||
exist_ok=True,
|
||||
))
|
||||
infer_main(InferArguments(model=merge_output_dir, load_data_args=True, max_batch_size=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_llm_quant('gptq')
|
||||
# test_vlm_quant('gptq')
|
||||
# test_audio_quant('gptq')
|
||||
# test_vlm_bnb_quant()
|
||||
# test_bert()
|
||||
# test_reward_model()
|
||||
test_fp8()
|
||||
# test_lora_merge_export_minimal()
|
||||
@@ -0,0 +1,45 @@
|
||||
def test_model_arch():
|
||||
import random
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
from swift.model import MODEL_MAPPING
|
||||
from swift.utils import JsonlWriter, safe_snapshot_download
|
||||
jsonl_writer = JsonlWriter('model_arch.jsonl')
|
||||
for i, (model_type, model_meta) in enumerate(MODEL_MAPPING.items()):
|
||||
if i < 0:
|
||||
continue
|
||||
arch_list = model_meta.architectures
|
||||
for model_group in model_meta.model_groups:
|
||||
model = random.choice(model_group.models).ms_model_id
|
||||
config_dict = None
|
||||
try:
|
||||
model_dir = safe_snapshot_download(model, download_model=False)
|
||||
config_dict = PretrainedConfig.get_config_dict(model_dir)[0]
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
msg = None
|
||||
if config_dict:
|
||||
arch = config_dict.get('architectures')
|
||||
if arch and arch[0] not in arch_list:
|
||||
msg = {
|
||||
'model_type': model_type,
|
||||
'model': model,
|
||||
'config_arch': arch,
|
||||
'architectures': arch_list
|
||||
}
|
||||
elif not arch and arch_list:
|
||||
msg = {
|
||||
'model_type': model_type,
|
||||
'model': model,
|
||||
'config_arch': arch,
|
||||
'architectures': arch_list
|
||||
}
|
||||
else:
|
||||
msg = {'msg': 'error', 'model_type': model_type, 'model': model, 'arch_list': arch_list}
|
||||
if msg:
|
||||
jsonl_writer.append(msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_model_arch()
|
||||
@@ -0,0 +1,207 @@
|
||||
import unittest
|
||||
|
||||
from swift.dataset import EncodePreprocessor, MessagesPreprocessor, PackingDataset, load_dataset
|
||||
from swift.model import get_processor
|
||||
from swift.template import get_template
|
||||
|
||||
|
||||
class TestDataPreprocess(unittest.TestCase):
|
||||
"""Lightweight data preprocessing tests (no model forward/backward).
|
||||
|
||||
These are fast tests suitable for CI. They cover:
|
||||
- SFT dataset encode (input_ids/labels)
|
||||
- Truncation/max_length
|
||||
- Data collator padding (attention_mask)
|
||||
- Multi-turn messages
|
||||
- Tool message
|
||||
- Packing dataset
|
||||
|
||||
Why these tests are needed:
|
||||
- Swift's data preprocessing pipeline is complex (template -> encode -> collate -> pack).
|
||||
NPU training failures often stem from shape/mask/label mismatches before the model
|
||||
even sees the data, not from operator issues.
|
||||
- The original tests/general/test_dataset.py and test_template.py use top-level
|
||||
functions and remote 7B models, so they are never run by unittest discovery
|
||||
and are too heavy for CI.
|
||||
"""
|
||||
|
||||
MODEL_PATH = 'Qwen/Qwen2-0.5B'
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.processor = get_processor(cls.MODEL_PATH)
|
||||
cls.template = get_template(cls.processor)
|
||||
cls.template.mode = 'train'
|
||||
cls.template.init_processor(cls.processor)
|
||||
|
||||
def _encode_dataset(self, dataset):
|
||||
encode_preprocessor = EncodePreprocessor(self.template)
|
||||
return encode_preprocessor(dataset, num_proc=1, load_from_cache_file=False, strict=False)
|
||||
|
||||
def test_sft_dataset_encode(self):
|
||||
dataset, _ = load_dataset(['AI-ModelScope/alpaca-gpt4-data-zh#20'], num_proc=1, strict=False)
|
||||
self.assertGreater(len(dataset), 0)
|
||||
encoded_dataset = self._encode_dataset(dataset)
|
||||
first = encoded_dataset[0]
|
||||
self.assertIn('input_ids', first)
|
||||
self.assertIn('labels', first)
|
||||
self.assertEqual(len(first['input_ids']), len(first['labels']))
|
||||
|
||||
def test_truncation_max_length(self):
|
||||
self.template.max_length = 128
|
||||
dataset, _ = load_dataset(['AI-ModelScope/alpaca-gpt4-data-zh#20'], num_proc=1, strict=False)
|
||||
encoded_dataset = self._encode_dataset(dataset)
|
||||
for row in encoded_dataset:
|
||||
self.assertLessEqual(len(row['input_ids']), self.template.max_length)
|
||||
self.template.max_length = None
|
||||
|
||||
def test_data_collator_padding(self):
|
||||
dataset, _ = load_dataset(['AI-ModelScope/alpaca-gpt4-data-zh#20'], num_proc=1, strict=False)
|
||||
encoded_dataset = self._encode_dataset(dataset)
|
||||
batch = [encoded_dataset[i] for i in range(4)]
|
||||
collated = self.template.data_collator(batch)
|
||||
self.assertIn('input_ids', collated)
|
||||
self.assertIn('labels', collated)
|
||||
self.assertIn('attention_mask', collated)
|
||||
self.assertEqual(collated['input_ids'].shape[0], 4)
|
||||
|
||||
def test_multi_turn_messages(self):
|
||||
multi_turn_row = {
|
||||
'messages': [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'What is Python?'
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'Python is a programming language.'
|
||||
},
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'What are its advantages?'
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'Python is easy to learn and use.'
|
||||
},
|
||||
]
|
||||
}
|
||||
encoded = self.template.encode(multi_turn_row, return_length=True)
|
||||
self.assertIn('input_ids', encoded)
|
||||
self.assertIn('labels', encoded)
|
||||
self.assertGreater(len(encoded['input_ids']), 0)
|
||||
self.assertEqual(len(encoded['input_ids']), len(encoded['labels']))
|
||||
|
||||
def test_tool_message(self):
|
||||
tool_row = {
|
||||
'messages': [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'What is the weather in Beijing?'
|
||||
},
|
||||
{
|
||||
'role':
|
||||
'assistant',
|
||||
'content':
|
||||
'',
|
||||
'tool_calls': [{
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'get_weather',
|
||||
'arguments': '{"city": "Beijing"}'
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
'role': 'tool',
|
||||
'content': '{"temperature": 25, "condition": "sunny"}'
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'The weather in Beijing is sunny with a temperature of 25 degrees.'
|
||||
},
|
||||
]
|
||||
}
|
||||
encoded = self.template.encode(tool_row, return_length=True)
|
||||
self.assertIn('input_ids', encoded)
|
||||
self.assertIn('labels', encoded)
|
||||
self.assertGreater(len(encoded['input_ids']), 0)
|
||||
|
||||
def test_packing_dataset(self):
|
||||
dataset, _ = load_dataset(['AI-ModelScope/alpaca-gpt4-data-zh#20'], num_proc=1, strict=False)
|
||||
encoded_dataset = self._encode_dataset(dataset)
|
||||
packing_dataset = PackingDataset(
|
||||
self.template,
|
||||
encoded_dataset,
|
||||
num_proc=1,
|
||||
strict=False,
|
||||
load_from_cache_file=False,
|
||||
packing_length=512,
|
||||
packing_num_proc=1,
|
||||
)
|
||||
self.assertGreater(len(packing_dataset), 0)
|
||||
packed = packing_dataset[0]
|
||||
self.assertIsInstance(packed, list)
|
||||
self.assertGreater(len(packed), 0)
|
||||
self.assertIn('input_ids', packed[0])
|
||||
self.assertIn('labels', packed[0])
|
||||
|
||||
|
||||
class TestRejectedMessagesPreprocess(unittest.TestCase):
|
||||
"""MessagesPreprocessor handling of rejected_messages (no model required)."""
|
||||
|
||||
def test_empty_rejected_messages_does_not_crash(self):
|
||||
"""A DPO row whose rejected_messages repair to empty must not crash.
|
||||
|
||||
The recursive preprocess() call returns None when rejected_messages is
|
||||
empty (the same graceful-skip path used for the main messages list), so
|
||||
subscripting it with ['messages'] raised TypeError and aborted the whole
|
||||
dataset map. Downstream already treats rejected_messages is None as
|
||||
'no rejected', so the row should fall back to None instead.
|
||||
"""
|
||||
row = {
|
||||
'messages': [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'Q'
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'good'
|
||||
},
|
||||
],
|
||||
'rejected_messages': [],
|
||||
}
|
||||
result = MessagesPreprocessor().preprocess(row)
|
||||
self.assertIsNotNone(result)
|
||||
self.assertIsNone(result['rejected_messages'])
|
||||
|
||||
def test_valid_rejected_messages_preserved(self):
|
||||
row = {
|
||||
'messages': [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'Q'
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'good'
|
||||
},
|
||||
],
|
||||
'rejected_messages': [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'Q'
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'bad'
|
||||
},
|
||||
],
|
||||
}
|
||||
result = MessagesPreprocessor().preprocess(row)
|
||||
self.assertEqual(result['rejected_messages'][-1]['content'], 'bad')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,90 @@
|
||||
from typing import List
|
||||
|
||||
from swift.dataset import load_dataset
|
||||
|
||||
|
||||
def _test_dataset(datasets: List[str], num_proc: int = 1, strict: bool = False, **kwargs):
|
||||
dataset = load_dataset(datasets, num_proc=num_proc, strict=strict, **kwargs)
|
||||
print(f'dataset[0]: {dataset[0]}')
|
||||
print(f'dataset[1]: {dataset[1]}')
|
||||
|
||||
|
||||
def test_sft():
|
||||
# swift/SlimOrca swift/cosmopedia-100k
|
||||
# _test_dataset(['lvjianjin/AdvertiseGen'])
|
||||
# _test_dataset(['AI-ModelScope/Duet-v0.5'])
|
||||
# _test_dataset(['swift/SlimOrca', 'swift/cosmopedia-100k'])
|
||||
# _test_dataset(['OmniData/Zhihu-KOL-More-Than-100-Upvotes'])
|
||||
# _test_dataset(['OmniData/Zhihu-KOL'])
|
||||
_test_dataset([
|
||||
'AI-ModelScope/alpaca-gpt4-data-zh#1000', 'AI-ModelScope/alpaca-gpt4-data-en#1000',
|
||||
'AI-ModelScope/LongAlpaca-12k#1000'
|
||||
])
|
||||
# _test_dataset(['swift/Infinity-Instruct:all'])
|
||||
# _test_dataset(['swift/sharegpt:all'])
|
||||
# _test_dataset(['AI-ModelScope/sharegpt_gpt4:all'])
|
||||
# _test_dataset(['iic/ms_bench'])
|
||||
# _test_dataset(['swift/tagengo-gpt4'])
|
||||
|
||||
|
||||
def test_mllm():
|
||||
# _test_dataset(['AI-ModelScope/ShareGPT4V:all'])
|
||||
# _test_dataset(['AI-ModelScope/LLaVA-Pretrain'])
|
||||
# _test_dataset(['swift/TextCaps'])
|
||||
# _test_dataset(['swift/RLAIF-V-Dataset:all'])
|
||||
# _test_dataset(['swift/OK-VQA_train'])
|
||||
# _test_dataset(['swift/OCR-VQA'])
|
||||
# _test_dataset(['swift/A-OKVQA'])
|
||||
# _test_dataset(['AI-ModelScope/MovieChat-1K-test'])
|
||||
_test_dataset([
|
||||
'AI-ModelScope/LaTeX_OCR:all', 'modelscope/coco_2014_caption:validation',
|
||||
'speech_asr/speech_asr_aishell1_trainsets:validation'
|
||||
],
|
||||
strict=False)
|
||||
# _test_dataset(['swift/VideoChatGPT:all'])
|
||||
# _test_dataset(['speech_asr/speech_asr_aishell1_trainsets:validation'])
|
||||
# _test_dataset(['AI-ModelScope/captcha-images'])
|
||||
# _test_dataset(['swift/gpt4v-dataset:all'])
|
||||
# _test_dataset(['modelscope/coco_2014_caption:validation'])
|
||||
# _test_dataset(['AI-ModelScope/LLaVA-Instruct-150K'], num_proc=16)
|
||||
|
||||
|
||||
def test_agent():
|
||||
_test_dataset(['swift/ToolBench'])
|
||||
# _test_dataset(['AI-ModelScope/ms_agent_for_agentfabric:all'])
|
||||
|
||||
|
||||
def test_dpo():
|
||||
_test_dataset(['AI-ModelScope/orpo-dpo-mix-40k'])
|
||||
_test_dataset(['AI-ModelScope/hh-rlhf:all'])
|
||||
_test_dataset(['AI-ModelScope/hh_rlhf_cn:all'])
|
||||
_test_dataset(['hjh0119/shareAI-Llama3-DPO-zh-en-emoji:all'])
|
||||
|
||||
|
||||
def test_kto():
|
||||
_test_dataset(['AI-ModelScope/ultrafeedback-binarized-preferences-cleaned-kto'])
|
||||
|
||||
|
||||
def test_pretrain():
|
||||
_test_dataset(['AI-ModelScope/ruozhiba:all'])
|
||||
|
||||
|
||||
def test_dataset_info():
|
||||
_test_dataset(['swift/self-cognition#500'], model_name='xiao huang', model_author='swift')
|
||||
# _test_dataset(['codefuse-ai/CodeExercise-Python-27k'])
|
||||
|
||||
|
||||
def test_cls():
|
||||
_test_dataset(['simpleai/HC3-Chinese:baike'])
|
||||
_test_dataset(['simpleai/HC3-Chinese:baike_cls'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_sft()
|
||||
# test_agent()
|
||||
# test_dpo()
|
||||
# test_kto()
|
||||
test_mllm()
|
||||
# test_pretrain()
|
||||
# test_dataset_info()
|
||||
# test_cls()
|
||||
@@ -0,0 +1,53 @@
|
||||
import os
|
||||
import torch
|
||||
import unittest
|
||||
|
||||
from swift.utils import get_device
|
||||
|
||||
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
|
||||
|
||||
|
||||
def test_qwen2():
|
||||
import os
|
||||
|
||||
from swift.model import get_model_processor
|
||||
model, tokenizer = get_model_processor('Qwen/Qwen2-7B-Instruct', load_model=False)
|
||||
print(f'model: {model}, tokenizer: {tokenizer}')
|
||||
# test hf
|
||||
model, tokenizer = get_model_processor('Qwen/Qwen2-7B-Instruct', load_model=False, use_hf=True)
|
||||
|
||||
model, tokenizer = get_model_processor(
|
||||
'Qwen/Qwen2-7B-Instruct', torch_dtype=torch.float32, device_map=get_device(), attn_impl='flash_attn')
|
||||
print(f'model: {model}, tokenizer: {tokenizer}')
|
||||
|
||||
|
||||
def test_modelscope_hub():
|
||||
from swift.model import get_model_processor
|
||||
model, tokenizer = get_model_processor('Qwen/Qwen2___5-Math-1___5B-Instruct/', load_model=False)
|
||||
|
||||
|
||||
class TestMolmo2Registration(unittest.TestCase):
|
||||
|
||||
def test_registration(self):
|
||||
from swift.model import MODEL_MAPPING, MLLMModelType
|
||||
from swift.template import TEMPLATE_MAPPING, TemplateType
|
||||
|
||||
model_meta = MODEL_MAPPING[MLLMModelType.molmo2]
|
||||
self.assertEqual(model_meta.template, TemplateType.molmo2)
|
||||
self.assertEqual(model_meta.model_arch.arch_name, 'molmo')
|
||||
self.assertIn('Molmo2ForConditionalGeneration', model_meta.architectures)
|
||||
|
||||
hf_model_ids = []
|
||||
for group in model_meta.model_groups:
|
||||
for model in group.models:
|
||||
hf_model_ids.append(model.hf_model_id)
|
||||
|
||||
self.assertIn('allenai/Molmo2-4B', hf_model_ids)
|
||||
self.assertIn('allenai/Molmo2-8B', hf_model_ids)
|
||||
self.assertIn('allenai/Molmo2-O-7B', hf_model_ids)
|
||||
self.assertIn(TemplateType.molmo2, TEMPLATE_MAPPING)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_qwen2()
|
||||
# test_modelscope_hub()
|
||||
@@ -0,0 +1,20 @@
|
||||
from swift.dataset import load_dataset
|
||||
|
||||
|
||||
def test_local_dataset():
|
||||
# please use git clone
|
||||
from swift.utils import git_clone_github
|
||||
model_dir = git_clone_github('https://www.modelscope.cn/datasets/swift/swift-sft-mixture.git')
|
||||
dataset = load_dataset(datasets=[f'{model_dir}:firefly'], streaming=True)[0]
|
||||
print(next(iter(dataset)))
|
||||
|
||||
|
||||
def test_hub_dataset():
|
||||
local_dataset = 'swift/swift-sft-mixture:firefly'
|
||||
dataset = load_dataset(datasets=[local_dataset], streaming=True)[0]
|
||||
print(next(iter(dataset)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_local_dataset()
|
||||
# test_hub_dataset()
|
||||
@@ -0,0 +1,79 @@
|
||||
from swift.dataset import EncodePreprocessor, load_dataset
|
||||
from swift.model import get_processor
|
||||
from swift.template import TemplateInputs, get_template
|
||||
|
||||
|
||||
def test_template():
|
||||
tokenizer = get_processor('Qwen/Qwen2-7B-Instruct')
|
||||
template = get_template(tokenizer)
|
||||
template_inputs = TemplateInputs.from_dict({
|
||||
'messages': [{
|
||||
'role': 'system',
|
||||
'content': 'AAA'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'BBB'
|
||||
}, {
|
||||
'role': 'assistant',
|
||||
'content': 'CCC'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'DDD'
|
||||
}]
|
||||
})
|
||||
inputs = template.encode(template_inputs)
|
||||
print(f'inputs.keys(): {inputs.keys()}')
|
||||
print(tokenizer.decode(inputs['input_ids']))
|
||||
|
||||
|
||||
def test_mllm():
|
||||
processor = get_processor('Qwen/Qwen2-VL-7B-Instruct')
|
||||
template = get_template(processor)
|
||||
template_inputs = TemplateInputs(
|
||||
chosen={
|
||||
'messages': [{
|
||||
'role': 'system',
|
||||
'content': 'AAA'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': '<image>BBB'
|
||||
}, {
|
||||
'role': 'assistant',
|
||||
'content': 'CCC'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'DDD'
|
||||
}],
|
||||
'images': ['http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png']
|
||||
})
|
||||
inputs = template.encode(template_inputs)
|
||||
print(f'inputs.keys(): {inputs.keys()}')
|
||||
print(template.safe_decode(inputs['input_ids']))
|
||||
|
||||
|
||||
def _test_dataset_map(model_id: str, dataset_id: str):
|
||||
tokenizer = get_processor(model_id)
|
||||
template = get_template(tokenizer)
|
||||
dataset = load_dataset([dataset_id], num_proc=2)[0]
|
||||
|
||||
# 1: 1500
|
||||
# 16: 10766.36 examples/s
|
||||
new_dataset = EncodePreprocessor(template)(dataset, num_proc=4)
|
||||
print(f'new_dataset: {new_dataset}')
|
||||
print(template.safe_decode(new_dataset[0]['input_ids']))
|
||||
print(template.safe_decode(new_dataset[1]['input_ids']))
|
||||
|
||||
|
||||
def test_llm_dataset_map():
|
||||
_test_dataset_map('Qwen/Qwen2-7B-Instruct', 'AI-ModelScope/alpaca-gpt4-data-zh')
|
||||
|
||||
|
||||
def test_mllm_dataset_map():
|
||||
_test_dataset_map('Qwen/Qwen2-VL-7B-Instruct', 'modelscope/coco_2014_caption:validation#100')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_template()
|
||||
test_mllm()
|
||||
test_llm_dataset_map()
|
||||
test_mllm_dataset_map()
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from swift.template import TemplateMeta
|
||||
|
||||
|
||||
def test_replace_system_preserves_non_string_elements():
|
||||
"""_replace_system must not drop list elements like ['bos_token_id'].
|
||||
|
||||
Templates such as ziya, bluelm and emu3_chat use
|
||||
``prefix=[['bos_token_id'], '{{SYSTEM}}']``. When no system message is
|
||||
provided the prefix is produced by _replace_system, which should keep every
|
||||
non-string element intact and only strip the placeholder from strings.
|
||||
"""
|
||||
meta = TemplateMeta(
|
||||
template_type='_test_replace_system_bug',
|
||||
prefix=[['bos_token_id'], '{{SYSTEM}}'],
|
||||
prompt=['{{QUERY}}'],
|
||||
chat_sep=['\n'],
|
||||
)
|
||||
# __post_init__ moves prefix to system_prefix and builds a no-system prefix
|
||||
# via _replace_system. The list element must survive.
|
||||
assert any(isinstance(p, list) for p in meta.prefix), (f'_replace_system dropped the bos_token_id list; '
|
||||
f'meta.prefix={meta.prefix!r}')
|
||||
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from modelscope import Model, check_local_model_is_latest
|
||||
|
||||
|
||||
class TestCheckModel(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
print(('Testing %s.%s' % (type(self).__name__, self._testMethodName)))
|
||||
self.tmp_dir = tempfile.TemporaryDirectory().name
|
||||
if not os.path.exists(self.tmp_dir):
|
||||
os.makedirs(self.tmp_dir)
|
||||
|
||||
def tearDown(self):
|
||||
import peft
|
||||
shutil.rmtree(self.tmp_dir)
|
||||
super().tearDown()
|
||||
|
||||
def test_check_model(self):
|
||||
model = Model.from_pretrained('damo/nlp_corom_sentence-embedding_chinese-base', revision='v1.0.0')
|
||||
self.assertFalse(check_local_model_is_latest(model.model_dir))
|
||||
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'save_steps': 50,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_sft():
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
from swift import SftArguments, sft_main
|
||||
sft_main(SftArguments(model='Qwen/Qwen2-7B-Instruct', dataset=['iic/ms_agent#2000'], loss_scale='react', **kwargs))
|
||||
|
||||
|
||||
def test_infer():
|
||||
from swift import InferArguments, infer_main
|
||||
ckpt_dir = 'output/Qwen2-7B-Instruct/vx-xxx/checkpoint-xxx'
|
||||
infer_main(InferArguments(adapters=[ckpt_dir]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_sft()
|
||||
# test_infer()
|
||||
@@ -0,0 +1,70 @@
|
||||
import os
|
||||
import torch
|
||||
from typing import Literal
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def _prepare(infer_backend: Literal['vllm', 'transformers', 'lmdeploy']):
|
||||
from swift.infer_engine import InferRequest
|
||||
if infer_backend == 'lmdeploy':
|
||||
from swift.infer_engine import LmdeployEngine
|
||||
engine = LmdeployEngine('OpenGVLab/InternVL2_5-2B', torch_dtype=torch.float32)
|
||||
elif infer_backend == 'transformers':
|
||||
from swift.infer_engine import TransformersEngine
|
||||
engine = TransformersEngine('Qwen/Qwen2-7B-Instruct', max_batch_size=16)
|
||||
elif infer_backend == 'vllm':
|
||||
from swift.infer_engine import VllmEngine
|
||||
engine = VllmEngine('Qwen/Qwen2-7B-Instruct')
|
||||
infer_requests = [
|
||||
# InferRequest([{'role': 'user', 'content': '晚上睡不着觉怎么办'}]) for i in range(100)
|
||||
InferRequest([{
|
||||
'role': 'user',
|
||||
'content': 'hello! who are you'
|
||||
}]) for i in range(100)
|
||||
]
|
||||
return engine, infer_requests
|
||||
|
||||
|
||||
def test_infer(infer_backend):
|
||||
from swift.infer_engine import RequestConfig
|
||||
from swift.metrics import InferStats
|
||||
engine, infer_requests = _prepare(infer_backend=infer_backend)
|
||||
request_config = RequestConfig(temperature=0)
|
||||
infer_stats = InferStats()
|
||||
|
||||
response_list = engine.infer(infer_requests, request_config=request_config, metrics=[infer_stats])
|
||||
|
||||
for response in response_list[:2]:
|
||||
print(response.choices[0].message.content)
|
||||
print(infer_stats.compute())
|
||||
|
||||
|
||||
def test_stream(infer_backend):
|
||||
from swift.infer_engine import RequestConfig
|
||||
from swift.metrics import InferStats
|
||||
engine, infer_requests = _prepare(infer_backend=infer_backend)
|
||||
infer_stats = InferStats()
|
||||
request_config = RequestConfig(temperature=0, stream=True, logprobs=True)
|
||||
|
||||
gen_list = engine.infer(infer_requests, request_config=request_config, metrics=[infer_stats])
|
||||
|
||||
for response in gen_list[0]:
|
||||
if response is None:
|
||||
continue
|
||||
print(response.choices[0].delta.content, end='', flush=True)
|
||||
print()
|
||||
print(infer_stats.compute())
|
||||
|
||||
gen_list = engine.infer(infer_requests, request_config=request_config, use_tqdm=True, metrics=[infer_stats])
|
||||
|
||||
for response in gen_list[0]:
|
||||
pass
|
||||
|
||||
print(infer_stats.compute())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_infer('transformers')
|
||||
# test_stream('transformers')
|
||||
@@ -0,0 +1,69 @@
|
||||
import os
|
||||
import torch
|
||||
from typing import Literal
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def _prepare(infer_backend: Literal['vllm', 'transformers', 'lmdeploy']):
|
||||
from swift.infer_engine import InferRequest
|
||||
|
||||
if infer_backend == 'lmdeploy':
|
||||
from swift.infer_engine import LmdeployEngine
|
||||
engine = LmdeployEngine('Qwen/Qwen2-7B-Instruct', torch_dtype=torch.float32)
|
||||
elif infer_backend == 'transformers':
|
||||
from swift.infer_engine import TransformersEngine
|
||||
engine = TransformersEngine('Qwen/Qwen2-7B-Instruct')
|
||||
elif infer_backend == 'vllm':
|
||||
from swift.infer_engine import VllmEngine
|
||||
engine = VllmEngine('Qwen/Qwen2-7B-Instruct')
|
||||
infer_requests = [
|
||||
InferRequest([{
|
||||
'role': 'user',
|
||||
'content': '晚上睡不着觉怎么办'
|
||||
}]),
|
||||
InferRequest([{
|
||||
'role': 'user',
|
||||
'content': 'hello! who are you'
|
||||
}])
|
||||
]
|
||||
return engine, infer_requests
|
||||
|
||||
|
||||
def test_infer(engine, infer_requests):
|
||||
from swift.infer_engine import RequestConfig
|
||||
from swift.metrics import InferStats
|
||||
|
||||
request_config = RequestConfig(temperature=0, logprobs=True, top_logprobs=2)
|
||||
infer_stats = InferStats()
|
||||
|
||||
response_list = engine.infer(infer_requests, request_config=request_config, metrics=[infer_stats])
|
||||
|
||||
for response in response_list[:2]:
|
||||
print(response.choices[0].message.content)
|
||||
print(infer_stats.compute())
|
||||
|
||||
|
||||
def test_stream(engine, infer_requests):
|
||||
from swift.infer_engine import RequestConfig
|
||||
from swift.metrics import InferStats
|
||||
|
||||
infer_stats = InferStats()
|
||||
request_config = RequestConfig(temperature=0, stream=True, logprobs=True, top_logprobs=2)
|
||||
|
||||
gen_list = engine.infer(infer_requests, request_config=request_config, metrics=[infer_stats])
|
||||
|
||||
for response in gen_list[0]:
|
||||
if response is None:
|
||||
continue
|
||||
print(response.choices[0].delta.content, end='', flush=True)
|
||||
|
||||
print(infer_stats.compute())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
engine, infer_requests = _prepare(infer_backend='transformers')
|
||||
test_infer(engine, infer_requests)
|
||||
test_stream(engine, infer_requests)
|
||||
@@ -0,0 +1,76 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def test_cli(infer_backend):
|
||||
from swift import InferArguments, infer_main
|
||||
args = InferArguments(model='Qwen/Qwen2-VL-7B-Instruct', infer_backend=infer_backend)
|
||||
infer_main(args)
|
||||
|
||||
|
||||
def test_cli_jinja(infer_backend):
|
||||
from swift import InferArguments, infer_main
|
||||
args = InferArguments(model='Qwen/Qwen2-VL-7B-Instruct', infer_backend=infer_backend, template_backend='jinja')
|
||||
infer_main(args)
|
||||
|
||||
|
||||
def test_dataset(infer_backend):
|
||||
from swift import InferArguments, infer_main
|
||||
args = InferArguments(
|
||||
model='Qwen/Qwen2-7B-Instruct',
|
||||
infer_backend=infer_backend,
|
||||
val_dataset=['AI-ModelScope/alpaca-gpt4-data-zh#10'],
|
||||
stream=True)
|
||||
infer_main(args)
|
||||
|
||||
|
||||
def test_mllm_dataset(infer_backend):
|
||||
from swift import InferArguments, infer_main
|
||||
args = InferArguments(
|
||||
model='Qwen/Qwen2-VL-7B-Instruct',
|
||||
infer_backend=infer_backend,
|
||||
val_dataset=['modelscope/coco_2014_caption:validation#1000'],
|
||||
stream=True)
|
||||
infer_main(args)
|
||||
|
||||
|
||||
def test_dataset_ddp():
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
from swift import InferArguments, infer_main
|
||||
args = InferArguments(
|
||||
model='Qwen/Qwen2-7B-Instruct', max_batch_size=64, val_dataset=['AI-ModelScope/alpaca-gpt4-data-zh#1000'])
|
||||
infer_main(args)
|
||||
|
||||
|
||||
def test_dataset_mp_ddp():
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
from swift import InferArguments, infer_main
|
||||
args = InferArguments(
|
||||
model='Qwen/Qwen2-7B-Instruct', max_batch_size=64, val_dataset=['AI-ModelScope/alpaca-gpt4-data-zh#1000'])
|
||||
infer_main(args)
|
||||
|
||||
|
||||
def test_emu3_gen(infer_backend):
|
||||
from swift import InferArguments, infer_main
|
||||
args = InferArguments(
|
||||
model='BAAI/Emu3-Gen',
|
||||
infer_backend=infer_backend,
|
||||
stream=False,
|
||||
use_chat_template=False,
|
||||
top_k=2048,
|
||||
max_new_tokens=40960)
|
||||
infer_main(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_cli('transformers')
|
||||
# test_cli_jinja('transformers')
|
||||
# test_dataset('transformers')
|
||||
# test_mllm_dataset('transformers')
|
||||
# test_dataset_ddp()
|
||||
# test_dataset_mp_ddp()
|
||||
test_emu3_gen('transformers')
|
||||
@@ -0,0 +1,10 @@
|
||||
from swift import InferArguments, infer_main
|
||||
|
||||
|
||||
def test_max_memory():
|
||||
infer_main(
|
||||
InferArguments(model='Qwen/Qwen2.5-7B-Instruct', max_memory='{0: "50GB", 1: "5GB"}', device_map='sequential'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_max_memory()
|
||||
@@ -0,0 +1,76 @@
|
||||
import os
|
||||
import torch
|
||||
from typing import Literal
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def _prepare(infer_backend: Literal['vllm', 'transformers', 'lmdeploy']):
|
||||
from swift.infer_engine import InferRequest
|
||||
if infer_backend == 'lmdeploy':
|
||||
from swift.infer_engine import LmdeployEngine
|
||||
engine = LmdeployEngine('Qwen/Qwen-VL-Chat', torch_dtype=torch.float32)
|
||||
elif infer_backend == 'transformers':
|
||||
from swift.infer_engine import TransformersEngine
|
||||
engine = TransformersEngine('Qwen/Qwen2-VL-7B-Instruct')
|
||||
elif infer_backend == 'vllm':
|
||||
from swift.infer_engine import VllmEngine
|
||||
engine = VllmEngine('Qwen/Qwen2-VL-7B-Instruct')
|
||||
infer_requests = [
|
||||
InferRequest([{
|
||||
'role': 'user',
|
||||
'content': '晚上睡不着觉怎么办'
|
||||
}]),
|
||||
InferRequest([{
|
||||
'role':
|
||||
'user',
|
||||
'content': [{
|
||||
'type': 'image_url',
|
||||
'image_url': 'http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png'
|
||||
}]
|
||||
}])
|
||||
]
|
||||
return engine, infer_requests
|
||||
|
||||
|
||||
def test_infer(engine, infer_requests):
|
||||
from swift.infer_engine import RequestConfig
|
||||
from swift.metrics import InferStats
|
||||
request_config = RequestConfig(temperature=0)
|
||||
infer_stats = InferStats()
|
||||
|
||||
response_list = engine.infer(infer_requests, request_config=request_config, metrics=[infer_stats])
|
||||
|
||||
for response in response_list[:2]:
|
||||
print(response.choices[0].message.content)
|
||||
print(infer_stats.compute())
|
||||
|
||||
|
||||
def test_stream(engine, infer_requests):
|
||||
from swift.infer_engine import RequestConfig
|
||||
from swift.metrics import InferStats
|
||||
infer_stats = InferStats()
|
||||
request_config = RequestConfig(temperature=0, stream=True, logprobs=True)
|
||||
|
||||
gen_list = engine.infer(infer_requests, request_config=request_config, metrics=[infer_stats])
|
||||
|
||||
for response in gen_list[0]:
|
||||
if response is None:
|
||||
continue
|
||||
print(response.choices[0].delta.content, end='', flush=True)
|
||||
print()
|
||||
print(infer_stats.compute())
|
||||
|
||||
gen_list = engine.infer(infer_requests, request_config=request_config, use_tqdm=True, metrics=[infer_stats])
|
||||
|
||||
for response in gen_list[0]:
|
||||
pass
|
||||
|
||||
print(infer_stats.compute())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
engine, infer_requests = _prepare(infer_backend='transformers')
|
||||
test_infer(engine, infer_requests)
|
||||
test_stream(engine, infer_requests)
|
||||
@@ -0,0 +1,55 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def test_engine():
|
||||
from swift.dataset import load_dataset
|
||||
from swift.infer_engine import RequestConfig, SglangEngine
|
||||
dataset = load_dataset('AI-ModelScope/alpaca-gpt4-data-zh#20')[0]
|
||||
engine = SglangEngine('Qwen/Qwen2.5-0.5B-Instruct')
|
||||
request_config = RequestConfig(max_tokens=1024)
|
||||
resp_list = engine.infer(list(dataset), request_config=request_config)
|
||||
for resp in resp_list[:5]:
|
||||
print(resp)
|
||||
resp_list = engine.infer(list(dataset), request_config=request_config)
|
||||
for resp in resp_list[:5]:
|
||||
print(resp)
|
||||
|
||||
|
||||
def test_engine_stream():
|
||||
from swift.dataset import load_dataset
|
||||
from swift.infer_engine import RequestConfig, SglangEngine
|
||||
dataset = load_dataset('AI-ModelScope/alpaca-gpt4-data-zh#1')[0]
|
||||
engine = SglangEngine('Qwen/Qwen2.5-0.5B-Instruct')
|
||||
request_config = RequestConfig(max_tokens=1024, stream=True)
|
||||
gen_list = engine.infer(list(dataset), request_config=request_config)
|
||||
for resp in gen_list[0]:
|
||||
if resp is None:
|
||||
continue
|
||||
print(resp.choices[0].delta.content, flush=True, end='')
|
||||
|
||||
|
||||
def test_infer():
|
||||
from swift import InferArguments, infer_main
|
||||
infer_main(
|
||||
InferArguments(model='Qwen/Qwen2.5-0.5B-Instruct', stream=True, infer_backend='sglang', max_new_tokens=2048))
|
||||
|
||||
|
||||
def test_eval():
|
||||
from swift import EvalArguments, eval_main
|
||||
eval_main(
|
||||
EvalArguments(
|
||||
model='Qwen/Qwen2-7B-Instruct',
|
||||
eval_dataset='arc_c',
|
||||
infer_backend='sglang',
|
||||
eval_backend='OpenCompass',
|
||||
))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_engine()
|
||||
# test_engine_stream()
|
||||
# test_infer()
|
||||
# test_eval()
|
||||
@@ -0,0 +1,73 @@
|
||||
import os
|
||||
|
||||
from swift import TransformersEngine
|
||||
from swift.infer_engine import InferRequest, RequestConfig
|
||||
from swift.metrics import InferStats
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
engine = TransformersEngine('Qwen/Qwen2-0.5B', max_batch_size=4)
|
||||
|
||||
|
||||
def test_batch_infer():
|
||||
infer_requests = [InferRequest([{'role': 'user', 'content': 'hello, who are you?'}]) for _ in range(4)]
|
||||
request_config = RequestConfig(temperature=0, max_tokens=32)
|
||||
infer_stats = InferStats()
|
||||
|
||||
response_list = engine.infer(infer_requests, request_config=request_config, metrics=[infer_stats])
|
||||
|
||||
assert len(response_list) == len(infer_requests)
|
||||
for response in response_list:
|
||||
assert len(response.choices) > 0
|
||||
assert response.choices[0].message.content is not None
|
||||
|
||||
stats = infer_stats.compute()
|
||||
assert stats['num_samples'] > 0
|
||||
assert stats['num_generated_tokens'] > 0
|
||||
|
||||
|
||||
def test_stream_infer():
|
||||
infer_requests = [InferRequest([{'role': 'user', 'content': 'What is 1+1? Answer briefly.'}])]
|
||||
request_config = RequestConfig(temperature=0, max_tokens=32, stream=True)
|
||||
infer_stats = InferStats()
|
||||
|
||||
gen_list = engine.infer(infer_requests, request_config=request_config, metrics=[infer_stats])
|
||||
|
||||
full_content = ''
|
||||
for chunk in gen_list[0]:
|
||||
if chunk is None:
|
||||
continue
|
||||
delta = chunk.choices[0].delta.content
|
||||
if delta:
|
||||
full_content += delta
|
||||
|
||||
assert len(full_content) > 0, 'Stream infer produced no content'
|
||||
|
||||
stats = infer_stats.compute()
|
||||
assert stats['num_samples'] > 0
|
||||
assert stats['num_generated_tokens'] > 0
|
||||
|
||||
|
||||
def test_single_infer_with_system():
|
||||
infer_requests = [
|
||||
InferRequest([{
|
||||
'role': 'system',
|
||||
'content': 'You are a helpful assistant.'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'Say hello in one word.'
|
||||
}])
|
||||
]
|
||||
request_config = RequestConfig(temperature=0, max_tokens=16)
|
||||
|
||||
response_list = engine.infer(infer_requests, request_config=request_config)
|
||||
|
||||
assert len(response_list) == 1
|
||||
assert len(response_list[0].choices) > 0
|
||||
assert response_list[0].choices[0].message.content is not None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_batch_infer()
|
||||
test_stream_infer()
|
||||
test_single_infer_with_system()
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ckpt_dir": "/mnt/workspace/yzhao/modelscope/swift/output/pai_test/checkpoint-6",
|
||||
"val_dataset_sample": 2,
|
||||
"load_dataset_config": true
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"model_type": "qwen-1_8b-chat",
|
||||
"dataset": "jd-sentiment-zh",
|
||||
"output_dir": "output/pai_test",
|
||||
"train_dataset_sample": 100,
|
||||
"eval_steps": 5
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
system,instruction,input,output
|
||||
00000,11111,22222,3.3
|
||||
,aaaaa,,ccccc
|
||||
,AAAAA,BBBBB,CCCCC
|
||||
|
@@ -0,0 +1,3 @@
|
||||
{"instruction": "11111", "input": "22222", "output": "33333", "history": [["aaaaa", "bbbbb"]], "system": "system123"}
|
||||
{"instruction": "aaaaa", "output": "ccccc"}
|
||||
{"instruction": "AAAAA", "input": "BBBBB", "output": "CCCCC"}
|
||||
@@ -0,0 +1,4 @@
|
||||
instruction,output
|
||||
11111,33333
|
||||
aaaaa,ccccc
|
||||
AAAAA,CCCCC
|
||||
|
@@ -0,0 +1,3 @@
|
||||
{"messages": [{"role": "system", "content": "00000"}, {"role": "user", "content": "11111"}, {"role": "assistant", "content": "22222"}]}
|
||||
{"messages": [{"role": "user", "content": "aaaaa"}, {"role": "assistant", "content": "bbbbb"}, {"role": "user", "content": "ccccc"}, {"role": "assistant", "content": "ddddd"}]}
|
||||
{"messages": [{"role": "user", "content": "AAAAA"}, {"role": "assistant", "content": "BBBBB"}, {"role": "user", "content": "CCCCC"}, {"role": "assistant", "content": "DDDDD"}]}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"conversations": [{"from": "system", "value": "00000"}, {"from": "user", "value": "11111"}, {"from": "assistant", "value": "22222"}]}
|
||||
{"conversations": [{"from": "user", "value": "aaaaa"}, {"from": "assistant", "value": "bbbbb"}, {"from": "user", "value": "ccccc"}, {"from": "assistant", "value": "ddddd"}]}
|
||||
{"conversations": [{"from": "user", "value": "AAAAA"}, {"from": "assistant", "value": "BBBBB"}, {"from": "user", "value": "CCCCC"}, {"from": "assistant", "value": "DDDDD"}]}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"query": "<img>https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg</img>55555", "response": "66666"}
|
||||
{"query": "<img>https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg</img><img>https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg</img>eeeee", "response": "fffff", "history": [["hello", "123"]]}
|
||||
{"query": "EEEEE", "response": "FFFFF", "history": [["AAAAA", "BBBBB"], ["CCCCC", "DDDDD"]]}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"query": "55555", "response": "66666", "images": ["https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg"]}
|
||||
{"query": "eeeee", "response": "fffff", "history": [], "images": ["https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg"]}
|
||||
{"query": "EEEEE", "response": "FFFFF", "history": [["AAAAA", "BBBBB"], ["CCCCC", "DDDDD"]], "images": ["https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg", "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg", "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg"]}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"query": "55555", "response": "66666", "images": ["https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg"]}
|
||||
{"query": "eeeee", "response": "fffff", "history": [], "images": ["https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg"]}
|
||||
{"query": "EEEEE", "response": "FFFFF", "history": [["AAAAA", "BBBBB"], ["CCCCC", "DDDDD"]], "images": ["https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg"]}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"system": "00000", "conversation": [{"human": "11111", "assistant": "22222"}]}
|
||||
{"conversation": [{"human": "aaaaa", "assistant": "bbbbb"}]}
|
||||
{"conversation": [{"human": "AAAAA", "assistant": "BBBBB"}, {"human": "CCCCC", "assistant": "DDDDD"}, {"human": "EEEEE", "assistant": "FFFFF"}]}
|
||||
@@ -0,0 +1,3 @@
|
||||
[{"system": "00000", "query": "55555", "response": "66666"},
|
||||
{"query": "eeeee", "response": "fffff", "history": []},
|
||||
{"query": "EEEEE", "response": "FFFFF", "history": [["AAAAA", "BBBBB"], ["CCCCC", "DDDDD"]]}]
|
||||
@@ -0,0 +1,3 @@
|
||||
{"system": "00000", "query": "55555", "response": "66666"}
|
||||
{"query": "eeeee", "response": "fffff", "history": []}
|
||||
{"query": "EEEEE", "response": "FFFFF", "history": [["AAAAA", "BBBBB"], ["CCCCC", "DDDDD"]]}
|
||||
@@ -0,0 +1,4 @@
|
||||
response
|
||||
11111
|
||||
aaaaa
|
||||
AAAAA
|
||||
|
@@ -0,0 +1,3 @@
|
||||
{"response": "11111"}
|
||||
{"response": "aaaaa"}
|
||||
{"response": "AAAAA"}
|
||||
@@ -0,0 +1,4 @@
|
||||
system,query,response
|
||||
00000,11111,22222
|
||||
,aaaaa,bbbbb
|
||||
,AAAAA,BBBBB
|
||||
|
@@ -0,0 +1,3 @@
|
||||
{"system": "00000", "query": "11111", "response": "22222"}
|
||||
{"query": "aaaaa", "response": "bbbbb"}
|
||||
{"query": "AAAAA", "response": "BBBBB"}
|
||||
@@ -0,0 +1,73 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import torch
|
||||
import unittest
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from swift.dataset import DatasetMeta, ResponsePreprocessor, load_dataset, register_dataset
|
||||
from swift.infer_engine import InferRequest, RequestConfig, TransformersEngine
|
||||
from swift.model import Model, ModelGroup, ModelMeta, register_model
|
||||
from swift.template import TemplateMeta, register_template
|
||||
|
||||
|
||||
class CustomPreprocessor(ResponsePreprocessor):
|
||||
prompt = """Task: Based on the given two sentences, provide a similarity score between 0.0 and 5.0.
|
||||
Sentence 1: {text1}
|
||||
Sentence 2: {text2}
|
||||
Similarity score: """
|
||||
|
||||
def preprocess(self, row: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
return super().preprocess({
|
||||
'query': self.prompt.format(text1=row['text1'], text2=row['text2']),
|
||||
'response': f"{row['label']:.1f}"
|
||||
})
|
||||
|
||||
|
||||
register_dataset(
|
||||
DatasetMeta(
|
||||
ms_dataset_id='swift/stsb',
|
||||
hf_dataset_id='SetFit/stsb',
|
||||
preprocess_func=CustomPreprocessor(),
|
||||
))
|
||||
|
||||
register_template(
|
||||
TemplateMeta(
|
||||
template_type='custom',
|
||||
prefix=['<extra_id_0>System\n{{SYSTEM}}\n'],
|
||||
prompt=['<extra_id_1>User\n{{QUERY}}\n<extra_id_1>Assistant\n'],
|
||||
chat_sep=['\n']))
|
||||
|
||||
register_model(
|
||||
ModelMeta(
|
||||
model_type='custom',
|
||||
model_groups=[
|
||||
ModelGroup([Model('AI-ModelScope/Nemotron-Mini-4B-Instruct', 'nvidia/Nemotron-Mini-4B-Instruct')])
|
||||
],
|
||||
template='custom',
|
||||
ignore_patterns=['nemo']))
|
||||
|
||||
|
||||
class TestCustom(unittest.TestCase):
|
||||
|
||||
def test_custom_model(self):
|
||||
infer_request = InferRequest(messages=[{'role': 'user', 'content': 'who are you?'}])
|
||||
request_config = RequestConfig(max_tokens=512, temperature=0)
|
||||
engine = TransformersEngine('AI-ModelScope/Nemotron-Mini-4B-Instruct', torch_dtype=torch.float16)
|
||||
response = engine.infer([infer_request], request_config)
|
||||
swift_response = response[0].choices[0].message.content
|
||||
|
||||
engine.template.template_backend = 'jinja'
|
||||
response = engine.infer([infer_request], request_config)
|
||||
jinja_response = response[0].choices[0].message.content
|
||||
assert swift_response == jinja_response, (f'swift_response: {swift_response}\njinja_response: {jinja_response}')
|
||||
print(f'response: {swift_response}')
|
||||
|
||||
def test_custom_dataset(self):
|
||||
dataset = load_dataset(['swift/stsb'])[0]
|
||||
assert len(dataset) == 5749
|
||||
assert list(dataset[0].keys()) == ['messages', 'dataset']
|
||||
print(f'dataset: {dataset}')
|
||||
print(f'dataset[0]: {dataset[0]}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,19 @@
|
||||
import unittest
|
||||
|
||||
from swift.dataset import load_dataset
|
||||
|
||||
|
||||
class TestDataset(unittest.TestCase):
|
||||
|
||||
def test_load_v_dataset(self):
|
||||
if not __name__ == '__main__':
|
||||
# ignore citest error in github
|
||||
return
|
||||
|
||||
for ds in ['m3it#1000', 'mantis-instruct#1000', 'llava-med-zh-instruct#1000']:
|
||||
ds = load_dataset(ds)
|
||||
assert len(ds[0]) > 800
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,80 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import transformers
|
||||
import unittest
|
||||
from packaging import version
|
||||
|
||||
from swift import ExportArguments, export_main
|
||||
|
||||
if __name__ == '__main__':
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
class TestTemplate(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
print(('Testing %s.%s' % (type(self).__name__, self._testMethodName)))
|
||||
self.tmp_dir = tempfile.TemporaryDirectory().name
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.exists(self.tmp_dir):
|
||||
shutil.rmtree(self.tmp_dir)
|
||||
super().tearDown()
|
||||
|
||||
@unittest.skip('swift2.0')
|
||||
def test_llama3(self):
|
||||
args = ExportArguments(model_type='llama3-8b-instruct', to_ollama=True, ollama_output_dir=self.tmp_dir)
|
||||
export_main(args)
|
||||
|
||||
template = ('TEMPLATE """{{ if .System }}<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n'
|
||||
'{{ .System }}<|eot_id|>{{ else }}<|begin_of_text|>{{ end }}{{ if .Prompt }}<|start_header_id|>user'
|
||||
'<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n'
|
||||
'{{ end }}{{ .Response }}<|eot_id|>"""')
|
||||
|
||||
stop = 'PARAMETER stop "<|eot_id|>"'
|
||||
|
||||
with open(os.path.join(self.tmp_dir, 'Modelfile'), 'r') as f:
|
||||
content = f.read()
|
||||
self.assertTrue(template in content)
|
||||
self.assertTrue(stop in content)
|
||||
|
||||
@unittest.skip('swift2.0')
|
||||
def test_chatglm4(self):
|
||||
if version.parse(transformers.__version__) >= version.parse('4.45'):
|
||||
return
|
||||
|
||||
args = ExportArguments(model_type='glm4-9b-chat', to_ollama=True, ollama_output_dir=self.tmp_dir)
|
||||
export_main(args)
|
||||
|
||||
template = ('TEMPLATE """{{ if .System }}[gMASK] <sop><|system|>\n{{ .System }}{{ else }}'
|
||||
'[gMASK] <sop>{{ end }}{{ if .Prompt }}<|user|>\n{{ .Prompt }}<|assistant|>\n'
|
||||
'{{ end }}{{ .Response }}<|user|>"""')
|
||||
|
||||
stop = 'PARAMETER stop "<|user|>"'
|
||||
|
||||
with open(os.path.join(self.tmp_dir, 'Modelfile'), 'r') as f:
|
||||
content = f.read()
|
||||
self.assertTrue(template in content)
|
||||
self.assertTrue(stop in content)
|
||||
|
||||
@unittest.skip('swift2.0')
|
||||
def test_qwen2(self):
|
||||
args = ExportArguments(model_type='qwen2-7b-instruct', to_ollama=True, ollama_output_dir=self.tmp_dir)
|
||||
export_main(args)
|
||||
|
||||
template = ('TEMPLATE """{{ if .System }}<|im_start|>system\n{{ .System }}<|im_end|>\n{{ else }}{{ end }}'
|
||||
'{{ if .Prompt }}<|im_start|>user\n{{ .Prompt }}<|im_end|>\n<|im_start|>assistant\n'
|
||||
'{{ end }}{{ .Response }}<|im_end|>"""')
|
||||
|
||||
stop = 'PARAMETER stop "<|im_end|>"'
|
||||
|
||||
with open(os.path.join(self.tmp_dir, 'Modelfile'), 'r') as f:
|
||||
content = f.read()
|
||||
self.assertTrue(template in content)
|
||||
self.assertTrue(stop in content)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,458 @@
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import torch
|
||||
import unittest
|
||||
from datasets import Dataset as HfDataset
|
||||
from functools import partial
|
||||
from modelscope import Model, MsDataset, snapshot_download
|
||||
from torch.nn.utils.rnn import pad_sequence
|
||||
from transformers import AutoTokenizer
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from swift import (InferArguments, RLHFArguments, SftArguments, Trainer, TrainingArguments, get_logger, infer_main,
|
||||
rlhf_main, sft_main)
|
||||
|
||||
NO_EVAL_HUMAN = True
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'per_device_eval_batch_size': 2,
|
||||
'save_steps': 5,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
class TestRun(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
print(f'Testing {type(self).__name__}.{self._testMethodName}')
|
||||
self._tmp_dir = tempfile.TemporaryDirectory()
|
||||
self.tmp_dir = self._tmp_dir.name
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmp_dir)
|
||||
|
||||
def test_template(self):
|
||||
if not __name__ == '__main__':
|
||||
# ignore citest error in github
|
||||
return
|
||||
torch.cuda.empty_cache()
|
||||
output = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen1.5-0.5B',
|
||||
tuner_type='full',
|
||||
dataset='DAMO_NLP/jd',
|
||||
val_dataset='DAMO_NLP/jd#20',
|
||||
streaming=True,
|
||||
max_steps=12,
|
||||
**kwargs))
|
||||
last_model_checkpoint = output['last_model_checkpoint']
|
||||
torch.cuda.empty_cache()
|
||||
result = infer_main(InferArguments(model=last_model_checkpoint, load_data_args=True, val_dataset_sample=2))
|
||||
assert len(result[0]['response']) < 20
|
||||
|
||||
def test_hf_hub(self):
|
||||
if not __name__ == '__main__':
|
||||
# ignore citest error in github
|
||||
return
|
||||
torch.cuda.empty_cache()
|
||||
train_dataset_fnames = [
|
||||
'alpaca.csv', 'chatml.jsonl', 'swift_pre.jsonl', 'swift_single.csv', 'swift_multi.jsonl',
|
||||
'swift_multi.json#2'
|
||||
]
|
||||
folder = os.path.join(os.path.dirname(__file__), 'data')
|
||||
dataset = [
|
||||
'llm-wizard/alpaca-gpt4-data-zh#20',
|
||||
'shibing624/alpaca-zh#20',
|
||||
] + [os.path.join(folder, fname) for fname in train_dataset_fnames]
|
||||
output = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen1.5-0.5B-Chat-GPTQ-Int4', tuner_type='lora', dataset=dataset, use_hf=True, **kwargs))
|
||||
last_model_checkpoint = output['last_model_checkpoint']
|
||||
torch.cuda.empty_cache()
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, val_dataset_sample=2))
|
||||
|
||||
@unittest.skip('avoid ci error')
|
||||
def test_basic(self):
|
||||
output_dir = 'output'
|
||||
quant_bits_list = [0, 4]
|
||||
train_dataset_fnames = [
|
||||
'alpaca.csv', 'chatml.jsonl', 'swift_pre.jsonl', 'swift_single.csv', 'swift_multi.jsonl',
|
||||
'swift_multi.json#2'
|
||||
]
|
||||
folder = os.path.join(os.path.dirname(__file__), 'data')
|
||||
dataset = [
|
||||
'AI-ModelScope/alpaca-gpt4-data-zh#20',
|
||||
'hurner/alpaca-gpt4-data-zh#20',
|
||||
] + [os.path.join(folder, fname) for fname in train_dataset_fnames]
|
||||
if not __name__ == '__main__':
|
||||
output_dir = self.tmp_dir
|
||||
quant_bits_list = [4]
|
||||
dataset = dataset[:2]
|
||||
for quant_bits in quant_bits_list:
|
||||
if quant_bits == 0:
|
||||
predict_with_generate = False
|
||||
quant_method = None
|
||||
else:
|
||||
predict_with_generate = True
|
||||
quant_method = 'bnb'
|
||||
sft_args = SftArguments(
|
||||
model='Qwen/Qwen2-0.5B-Instruct',
|
||||
quant_bits=quant_bits,
|
||||
eval_steps=5,
|
||||
adam_beta2=0.95,
|
||||
quant_method=quant_method,
|
||||
predict_with_generate=predict_with_generate,
|
||||
dataset=dataset,
|
||||
val_dataset='DAMO_NLP/jd#20',
|
||||
output_dir=output_dir,
|
||||
download_mode='force_redownload',
|
||||
include_num_input_tokens_seen=True,
|
||||
gradient_checkpointing=True,
|
||||
**kwargs)
|
||||
torch.cuda.empty_cache()
|
||||
output = sft_main(sft_args)
|
||||
print(output)
|
||||
best_model_checkpoint = output['best_model_checkpoint']
|
||||
print(f'best_model_checkpoint: {best_model_checkpoint}')
|
||||
if __name__ == '__main__':
|
||||
infer_args = InferArguments(
|
||||
adapters=best_model_checkpoint,
|
||||
merge_lora={
|
||||
0: True,
|
||||
4: False
|
||||
}[quant_bits],
|
||||
load_data_args=NO_EVAL_HUMAN,
|
||||
val_dataset_sample=5)
|
||||
torch.cuda.empty_cache()
|
||||
result = infer_main(infer_args)
|
||||
print(result)
|
||||
# if __name__ == '__main__':
|
||||
# app_ui_main(infer_args)
|
||||
|
||||
def test_vl_audio(self):
|
||||
output_dir = 'output'
|
||||
if not __name__ == '__main__':
|
||||
# ignore citest error in github
|
||||
return
|
||||
model_type_list = ['Qwen/Qwen-VL-Chat', 'Qwen/Qwen-Audio-Chat']
|
||||
dataset_list = [
|
||||
'modelscope/coco_2014_caption:validation#100', 'speech_asr/speech_asr_aishell1_trainsets:validation#100'
|
||||
]
|
||||
for model, dataset in zip(model_type_list, dataset_list):
|
||||
sft_args = SftArguments(
|
||||
model=model,
|
||||
eval_steps=5,
|
||||
dataset=[dataset],
|
||||
output_dir=output_dir,
|
||||
gradient_checkpointing=True,
|
||||
lazy_tokenize=True,
|
||||
disable_tqdm=True,
|
||||
**kwargs)
|
||||
torch.cuda.empty_cache()
|
||||
output = sft_main(sft_args)
|
||||
print(output)
|
||||
best_model_checkpoint = output['best_model_checkpoint']
|
||||
print(f'best_model_checkpoint: {best_model_checkpoint}')
|
||||
infer_args = InferArguments(
|
||||
adapters=best_model_checkpoint,
|
||||
load_data_args=True,
|
||||
stream={
|
||||
'Qwen/Qwen-VL-Chat': True,
|
||||
'Qwen/Qwen-Audio-Chat': False
|
||||
}[model],
|
||||
val_dataset_sample=5)
|
||||
torch.cuda.empty_cache()
|
||||
result = infer_main(infer_args)
|
||||
print(result)
|
||||
|
||||
def test_custom_dataset(self):
|
||||
if not __name__ == '__main__':
|
||||
# ignore citest error in github
|
||||
return
|
||||
train_dataset_fnames = [
|
||||
'alpaca.csv', 'chatml.jsonl', 'swift_pre.jsonl', 'swift_single.csv', 'swift_multi.jsonl',
|
||||
'swift_multi.json', 'sharegpt.jsonl'
|
||||
]
|
||||
val_dataset_fnames = [
|
||||
'alpaca.jsonl',
|
||||
'alpaca2.csv',
|
||||
'conversations.jsonl',
|
||||
'swift_pre.csv',
|
||||
'swift_single.jsonl',
|
||||
# 'swift_#:#.jsonl#3'
|
||||
]
|
||||
folder = os.path.join(os.path.dirname(__file__), 'data')
|
||||
resume_from_checkpoint = None
|
||||
train_kwargs = kwargs.copy()
|
||||
train_kwargs.pop('num_train_epochs')
|
||||
for num_train_epochs in [1, 2]:
|
||||
sft_args = SftArguments(
|
||||
model='Qwen/Qwen-7B-Chat',
|
||||
dataset=['swift/self-cognition#20'] + [os.path.join(folder, fname) for fname in train_dataset_fnames],
|
||||
val_dataset=[os.path.join(folder, fname) for fname in val_dataset_fnames],
|
||||
resume_from_checkpoint=resume_from_checkpoint,
|
||||
num_train_epochs=num_train_epochs,
|
||||
model_name='小黄',
|
||||
model_author='魔搭',
|
||||
**train_kwargs)
|
||||
|
||||
torch.cuda.empty_cache()
|
||||
result = sft_main(sft_args)
|
||||
best_model_checkpoint = result['best_model_checkpoint']
|
||||
resume_from_checkpoint = result['last_model_checkpoint']
|
||||
|
||||
for load_args in [True, False]:
|
||||
infer_kwargs = {}
|
||||
if load_args is False:
|
||||
args_json = os.path.join(best_model_checkpoint, 'args.json')
|
||||
assert os.path.exists(args_json)
|
||||
os.remove(args_json)
|
||||
infer_kwargs = {'model': 'Qwen/Qwen-7B-Chat'}
|
||||
infer_args = InferArguments(
|
||||
adapters=best_model_checkpoint,
|
||||
load_data_args=load_args and NO_EVAL_HUMAN,
|
||||
merge_lora=load_args,
|
||||
val_dataset=[os.path.join(folder, fname) for fname in val_dataset_fnames],
|
||||
**infer_kwargs)
|
||||
torch.cuda.empty_cache()
|
||||
infer_main(infer_args)
|
||||
|
||||
def test_rlhf(self):
|
||||
if not __name__ == '__main__':
|
||||
# ignore citest error in github
|
||||
return
|
||||
torch.cuda.empty_cache()
|
||||
# llm rlhf
|
||||
#
|
||||
rlhf_types = ['dpo', 'orpo', 'simpo', 'kto', 'cpo', 'rm', 'ppo']
|
||||
for rlhf_type in rlhf_types:
|
||||
dataset = ('AI-ModelScope/hh_rlhf_cn:harmless_base_cn#100'
|
||||
if rlhf_type != 'kto' else 'AI-ModelScope/ultrafeedback-binarized-preferences-cleaned-kto#100')
|
||||
train_kwargs = {}
|
||||
if rlhf_type == 'ppo':
|
||||
train_kwargs['reward_model'] = 'Qwen/Qwen2-1.5B-Instruct'
|
||||
output = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type=rlhf_type,
|
||||
model='Qwen/Qwen2-1.5B-Instruct',
|
||||
dataset=dataset,
|
||||
eval_steps=5,
|
||||
split_dataset_ratio=0.05,
|
||||
**train_kwargs,
|
||||
**kwargs))
|
||||
if rlhf_type == 'ppo':
|
||||
model_checkpoint = output['last_model_checkpoint']
|
||||
else:
|
||||
model_checkpoint = output['best_model_checkpoint']
|
||||
|
||||
torch.cuda.empty_cache()
|
||||
infer_main(InferArguments(adapters=model_checkpoint, load_data_args=True))
|
||||
|
||||
# mllm rlhf
|
||||
visual_rlhf_types = ['dpo', 'orpo', 'simpo', 'cpo', 'rm']
|
||||
test_model = [
|
||||
'OpenGVLab/InternVL2-2B', 'Qwen/Qwen2-VL-2B-Instruct', 'llava-hf/llava-v1.6-mistral-7b-hf',
|
||||
'AI-ModelScope/Florence-2-base-ft'
|
||||
] # decoder only and encoder-decoder
|
||||
for rlhf_type in visual_rlhf_types:
|
||||
for model in test_model:
|
||||
dataset_name = 'swift/RLAIF-V-Dataset#100'
|
||||
output = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type=rlhf_type,
|
||||
model=model,
|
||||
dataset=dataset_name,
|
||||
eval_steps=5,
|
||||
dataset_num_proc=16,
|
||||
**kwargs))
|
||||
best_model_checkpoint = output['best_model_checkpoint']
|
||||
torch.cuda.empty_cache()
|
||||
infer_main(InferArguments(adapters=best_model_checkpoint, load_data_args=True, val_dataset_sample=2))
|
||||
|
||||
def test_loss_matching(self):
|
||||
output_dir = 'output'
|
||||
if not __name__ == '__main__':
|
||||
# ignore citest error in github
|
||||
return
|
||||
losses = []
|
||||
for use_swift_lora in [False, True]:
|
||||
bool_var = use_swift_lora
|
||||
torch.cuda.empty_cache()
|
||||
output = sft_main([
|
||||
'--model', 'Qwen/Qwen-7B-Chat', '--save_steps', '5', '--dataset',
|
||||
'AI-ModelScope/leetcode-solutions-python#200', '--output_dir', output_dir, '--gradient_checkpointing',
|
||||
'true', '--max_new_tokens', '100', '--attn_impl', 'flash_attn', '--target_modules', 'all-linear',
|
||||
'--seed', '0', '--lora_bias', 'all', '--modules_to_save', 'lm_head', '--use_swift_lora',
|
||||
str(use_swift_lora), '--num_train_epochs', '1', '--gradient_accumulation_steps', '16'
|
||||
])
|
||||
best_model_checkpoint = output['best_model_checkpoint']
|
||||
print(f'best_model_checkpoint: {best_model_checkpoint}')
|
||||
load_data_args = str(bool_var or NO_EVAL_HUMAN)
|
||||
if load_data_args:
|
||||
val_dataset_sample = 2
|
||||
else:
|
||||
val_dataset_sample = -1
|
||||
torch.cuda.empty_cache()
|
||||
infer_main([
|
||||
'--adapters', best_model_checkpoint, '--val_dataset_sample',
|
||||
str(val_dataset_sample), '--max_new_tokens', '100', '--attn_impl', 'eager', '--merge_lora',
|
||||
str(bool_var), '--load_data_args',
|
||||
str(load_data_args)
|
||||
])
|
||||
loss = output['log_history'][-1]['train_loss']
|
||||
losses.append(loss)
|
||||
self.assertTrue(abs(losses[0] - losses[1]) < 5e-4)
|
||||
print(f'swift_loss: {losses[0]}')
|
||||
print(f'peft_loss: {losses[1]}')
|
||||
self.assertTrue(0.95 <= losses[0] <= 1)
|
||||
|
||||
def test_pai_compat(self):
|
||||
if not __name__ == '__main__':
|
||||
# ignore citest error in github
|
||||
return
|
||||
from swift import infer_main, sft_main
|
||||
os.environ['PAI_TRAINING_JOB_ID'] = '123456'
|
||||
folder = os.path.join(os.path.dirname(__file__), 'config')
|
||||
tensorboard_dir = os.path.join('output/pai_test', 'pai_tensorboard')
|
||||
os.environ['PAI_OUTPUT_TENSORBOARD'] = tensorboard_dir
|
||||
sft_json = os.path.join(folder, 'sft.json')
|
||||
infer_json = os.path.join(folder, 'infer.json')
|
||||
torch.cuda.empty_cache()
|
||||
output = sft_main([sft_json])
|
||||
print()
|
||||
infer_args = {
|
||||
'adapters': output['best_model_checkpoint'],
|
||||
'val_dataset_sample': 2,
|
||||
'load_data_args': True,
|
||||
}
|
||||
import json
|
||||
with open(infer_json, 'w') as f:
|
||||
json.dump(infer_args, f, ensure_ascii=False, indent=4)
|
||||
torch.cuda.empty_cache()
|
||||
infer_main([infer_json])
|
||||
os.environ.pop('PAI_TRAINING_JOB_ID')
|
||||
|
||||
|
||||
def data_collate_fn(batch: List[Dict[str, Any]], tokenizer) -> Dict[str, torch.Tensor]:
|
||||
# text-classification
|
||||
assert tokenizer.pad_token_id is not None
|
||||
input_ids = [torch.tensor(b['input_ids']) for b in batch]
|
||||
labels = torch.tensor([b['labels'] for b in batch])
|
||||
attention_mask = [torch.ones(len(input_ids[i]), dtype=torch.int64) for i in range(len(input_ids))]
|
||||
|
||||
input_ids = pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id)
|
||||
attention_mask = pad_sequence(attention_mask, batch_first=True, padding_value=0)
|
||||
return {'input_ids': input_ids, 'attention_mask': attention_mask, 'labels': labels}
|
||||
|
||||
|
||||
class BertTrainer(Trainer):
|
||||
|
||||
def compute_loss(self, model, inputs, return_outputs=False):
|
||||
outputs = model(**inputs)
|
||||
loss = outputs.loss
|
||||
if loss is None:
|
||||
logits, loss = list(outputs.logits)
|
||||
return (loss, outputs) if return_outputs else loss
|
||||
|
||||
|
||||
class TestTrainer(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._tmp_dir = tempfile.TemporaryDirectory()
|
||||
self.tmp_dir = self._tmp_dir.name
|
||||
# self.tmp_dir = 'test'
|
||||
logger.info(f'self.tmp_dir: {self.tmp_dir}')
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.isdir(self.tmp_dir):
|
||||
shutil.rmtree(self.tmp_dir)
|
||||
# api = HubApi()
|
||||
# api.delete_model(self.hub_model_id)
|
||||
# logger.info(f'delete model: {self.hub_model_id}')
|
||||
|
||||
def test_trainer(self):
|
||||
self.hub_model_id = 'test_trainer2'
|
||||
logger.info(f'self.hub_model_id: {self.hub_model_id}')
|
||||
self.tmp_dir = 'output/damo/nlp_structbert_backbone_base_std'
|
||||
push_to_hub = True
|
||||
if not __name__ == '__main__':
|
||||
# ignore citest error in github
|
||||
return
|
||||
model_id = 'damo/nlp_structbert_backbone_base_std'
|
||||
model_dir = snapshot_download(model_id, 'master')
|
||||
tokenizer = AutoTokenizer.from_pretrained(model_dir)
|
||||
dataset = MsDataset.load('clue', subset_name='tnews')
|
||||
num_labels = max(dataset['train']['label']) + 1
|
||||
model = Model.from_pretrained(model_dir, task='text-classification', num_labels=num_labels)
|
||||
train_dataset, val_dataset = dataset['train'].to_hf_dataset(), dataset['validation'].to_hf_dataset()
|
||||
train_dataset: HfDataset = train_dataset.select(range(100))
|
||||
val_dataset: HfDataset = val_dataset.select(range(20))
|
||||
|
||||
#
|
||||
def tokenize_func(examples):
|
||||
data = tokenizer(examples['sentence'], return_attention_mask=False)
|
||||
examples['input_ids'] = data['input_ids']
|
||||
examples['labels'] = examples['label']
|
||||
del examples['sentence'], examples['label']
|
||||
return examples
|
||||
|
||||
train_dataset = train_dataset.map(tokenize_func)
|
||||
val_dataset = val_dataset.map(tokenize_func)
|
||||
|
||||
data_collator = partial(data_collate_fn, tokenizer=tokenizer)
|
||||
for save_only_model in [True, False]:
|
||||
trainer_args = TrainingArguments(
|
||||
self.tmp_dir,
|
||||
do_train=True,
|
||||
do_eval=True,
|
||||
num_train_epochs=1,
|
||||
evaluation_strategy='steps',
|
||||
save_strategy='steps',
|
||||
per_device_train_batch_size=4,
|
||||
per_device_eval_batch_size=4,
|
||||
push_to_hub=push_to_hub,
|
||||
hub_token=None, # use env var
|
||||
hub_private_repo=True,
|
||||
hub_strategy='every_save',
|
||||
hub_model_id=self.hub_model_id,
|
||||
overwrite_output_dir=True,
|
||||
save_steps=10,
|
||||
save_total_limit=2,
|
||||
metric_for_best_model='loss',
|
||||
greater_is_better=False,
|
||||
report_to=['tensorboard'],
|
||||
gradient_accumulation_steps=1,
|
||||
logging_steps=5,
|
||||
eval_steps=10,
|
||||
save_safetensors=False,
|
||||
save_only_model=save_only_model)
|
||||
trainer_args._n_gpu = 1
|
||||
trainer = BertTrainer(model, trainer_args, data_collator, train_dataset, val_dataset, tokenizer)
|
||||
self.hub_model_id = trainer_args.hub_model_id
|
||||
trainer.train()
|
||||
if trainer_args.push_to_hub:
|
||||
trainer.push_to_hub()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# TestRun().test_template()
|
||||
# TestRun().test_hf_hub()
|
||||
# TestRun().test_basic()
|
||||
# TestRun().test_custom_dataset()
|
||||
# TestRun().test_vl_audio()
|
||||
# TestRun().test_loss_matching()
|
||||
#
|
||||
# TestRun().test_rlhf()
|
||||
unittest.main()
|
||||
@@ -0,0 +1,109 @@
|
||||
import os
|
||||
import torch
|
||||
import unittest
|
||||
|
||||
from swift.infer_engine import RequestConfig, TransformersEngine
|
||||
from swift.model import get_processor
|
||||
from swift.template import get_template
|
||||
from swift.utils import get_logger, seed_everything
|
||||
|
||||
# os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
# os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['SWIFT_DEBUG'] = '1'
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
def _infer_model(engine, system=None, messages=None):
|
||||
seed_everything(42)
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0)
|
||||
if messages is None:
|
||||
messages = []
|
||||
if system is not None:
|
||||
messages += [{'role': 'system', 'content': system}]
|
||||
messages += [{'role': 'user', 'content': '你好'}]
|
||||
resp = engine.infer([{'messages': messages}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}, {'role': 'user', 'content': '<image>这是什么'}]
|
||||
resp = engine.infer([{
|
||||
'messages': messages,
|
||||
}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}]
|
||||
logger.info(f'model: {engine.model_info.model_name}, messages: {messages}')
|
||||
return response
|
||||
|
||||
|
||||
class TestTemplate(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), reason='GPTQ is only available on GPU')
|
||||
def test_template(self):
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-3B-Instruct-GPTQ-Int4')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
def test_tool_message_join(self):
|
||||
from copy import deepcopy
|
||||
|
||||
from swift.agent_template import agent_template_map
|
||||
|
||||
messages = [
|
||||
# first round
|
||||
{
|
||||
'role': 'user',
|
||||
'content': 'user1'
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'assistant1'
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'assistant2'
|
||||
},
|
||||
{
|
||||
'role': 'tool',
|
||||
'content': 'tool1'
|
||||
},
|
||||
# second round
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': 'assistant3'
|
||||
},
|
||||
{
|
||||
'role': 'tool',
|
||||
'content': 'tool2'
|
||||
},
|
||||
{
|
||||
'role': 'tool',
|
||||
'content': 'tool3'
|
||||
},
|
||||
]
|
||||
|
||||
# testing two template type.
|
||||
tokenizer = get_processor('Qwen/Qwen2.5-7B-Instruct')
|
||||
template = get_template(tokenizer)
|
||||
for agent_template_type in ('react_zh', 'qwen_zh'):
|
||||
template._agent_template = agent_template_type
|
||||
agent_template = template.agent_template
|
||||
observation = agent_template.keyword.observation
|
||||
test_messages = deepcopy(messages)
|
||||
test_messages[2]['content'] = 'assistant2' + observation
|
||||
test_messages[4]['content'] = (
|
||||
agent_template.keyword.action + agent_template.keyword.action_input + 'assistant3' + observation)
|
||||
encoded = template.encode({'messages': test_messages})
|
||||
res = template.safe_decode(encoded['input_ids'])
|
||||
|
||||
ground_truth = (
|
||||
'<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n'
|
||||
'<|im_start|>user\nuser1<|im_end|>\n'
|
||||
f'<|im_start|>assistant\nassistant1assistant2{observation}tool1'
|
||||
f'{agent_template.keyword.action}{agent_template.keyword.action_input}assistant3'
|
||||
f'{observation}tool2\n{observation}tool3\n')
|
||||
assert res == ground_truth
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,28 @@
|
||||
import unittest
|
||||
|
||||
from swift.dataset import load_dataset
|
||||
from swift.utils import lower_bound
|
||||
|
||||
|
||||
class TestLlmUtils(unittest.TestCase):
|
||||
|
||||
def test_count_startswith(self):
|
||||
arr = [-100] * 1000 + list(range(1000))
|
||||
self.assertTrue(lower_bound(0, len(arr), lambda i: arr[i] != -100) == 1000)
|
||||
|
||||
def test_count_endswith(self):
|
||||
arr = list(range(1000)) + [-100] * 1000
|
||||
self.assertTrue(lower_bound(0, len(arr), lambda i: arr[i] == -100) == 1000)
|
||||
|
||||
@unittest.skip('avoid ci error')
|
||||
def test_dataset(self):
|
||||
dataset = load_dataset(['AI-ModelScope/alpaca-gpt4-data-zh#1000', 'AI-ModelScope/alpaca-gpt4-data-en#200'],
|
||||
num_proc=4,
|
||||
strict=False,
|
||||
download_mode='force_redownload')
|
||||
print(f'dataset[0]: {dataset[0]}')
|
||||
print(f'dataset[1]: {dataset[1]}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,5 @@
|
||||
from swift.arguments import WebUIArguments
|
||||
from swift.ui import webui_main
|
||||
|
||||
if __name__ == '__main__':
|
||||
webui_main(WebUIArguments())
|
||||
@@ -0,0 +1,79 @@
|
||||
import os
|
||||
|
||||
from swift.megatron import MegatronExportArguments, megatron_export_main
|
||||
|
||||
os.environ['NVTE_DEBUG'] = '1'
|
||||
os.environ['NVTE_DEBUG_LEVEL'] = '2'
|
||||
|
||||
os.environ['SWIFT_TEST_CONVERT_PRECISION'] = '1'
|
||||
|
||||
|
||||
def test_to_mcore():
|
||||
megatron_export_main(
|
||||
MegatronExportArguments(
|
||||
model='Qwen/Qwen2.5-7B-Instruct',
|
||||
output_dir='Qwen2.5-7B-Instruct-mcore',
|
||||
to_mcore=True,
|
||||
exist_ok=True,
|
||||
tensor_model_parallel_size=2,
|
||||
test_convert_precision=True))
|
||||
|
||||
|
||||
def test_cp():
|
||||
megatron_export_main(
|
||||
MegatronExportArguments(
|
||||
model='Qwen/Qwen3.5-4B',
|
||||
to_mcore=True,
|
||||
exist_ok=True,
|
||||
attention_backend='flash',
|
||||
padding_free=True,
|
||||
context_parallel_size=2,
|
||||
tensor_model_parallel_size=2,
|
||||
pipeline_model_parallel_size=2,
|
||||
test_convert_precision=True))
|
||||
|
||||
|
||||
def test_to_hf():
|
||||
megatron_export_main(
|
||||
MegatronExportArguments(
|
||||
mcore_model='Qwen3-30B-A3B-mcore',
|
||||
to_hf=True,
|
||||
exist_ok=True,
|
||||
tensor_model_parallel_size=2,
|
||||
pipeline_model_parallel_size=2,
|
||||
expert_model_parallel_size=2,
|
||||
test_convert_precision=True))
|
||||
|
||||
|
||||
def test_peft_to_mcore():
|
||||
megatron_export_main(
|
||||
MegatronExportArguments(
|
||||
model='Qwen/Qwen3-30B-A3B',
|
||||
adapters=['megatron_output/Qwen3-30B-A3B/vx-xxx/checkpoint-xxx-hf'],
|
||||
merge_lora=False,
|
||||
to_mcore=True,
|
||||
exist_ok=True,
|
||||
tensor_model_parallel_size=2,
|
||||
expert_model_parallel_size=4,
|
||||
test_convert_precision=True))
|
||||
|
||||
|
||||
def test_peft_to_hf():
|
||||
megatron_export_main(
|
||||
MegatronExportArguments(
|
||||
mcore_model='Qwen3-30B-A3B-mcore',
|
||||
mcore_adapter='megatron_output/Qwen3-30B-A3B/vx-xxx/checkpoint-xxx',
|
||||
merge_lora=False,
|
||||
to_hf=True,
|
||||
exist_ok=True,
|
||||
tensor_model_parallel_size=2,
|
||||
expert_model_parallel_size=2,
|
||||
test_convert_precision=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_to_mcore()
|
||||
test_cp()
|
||||
# test_to_hf()
|
||||
# test_peft_to_mcore()
|
||||
# test_peft_to_hf()
|
||||
@@ -0,0 +1,60 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
|
||||
|
||||
def test_embedding():
|
||||
from swift.megatron import MegatronSftArguments, megatron_sft_main
|
||||
megatron_sft_main(
|
||||
MegatronSftArguments(
|
||||
model='Qwen/Qwen3-Embedding-0.6B',
|
||||
task_type='embedding',
|
||||
dataset=['sentence-transformers/stsb:positive'],
|
||||
split_dataset_ratio=0.01,
|
||||
micro_batch_size=4,
|
||||
tensor_model_parallel_size=2,
|
||||
tuner_type='lora',
|
||||
num_train_epochs=1,
|
||||
recompute_granularity='full',
|
||||
recompute_method='uniform',
|
||||
recompute_num_layers=1,
|
||||
loss_type='infonce',
|
||||
vit_attn_impl='flash_attn',
|
||||
max_length=2048,
|
||||
eval_iters=5,
|
||||
save_steps=5,
|
||||
no_save_optim=True,
|
||||
no_save_rng=True,
|
||||
sequence_parallel=True,
|
||||
finetune=True))
|
||||
|
||||
|
||||
def test_reranker():
|
||||
from swift.megatron import MegatronSftArguments, megatron_sft_main
|
||||
megatron_sft_main(
|
||||
MegatronSftArguments(
|
||||
model='Qwen/Qwen3-Reranker-4B',
|
||||
tuner_type='lora',
|
||||
load_from_cache_file=True,
|
||||
num_train_epochs=1,
|
||||
task_type='generative_reranker',
|
||||
dataset=['MTEB/scidocs-reranking#2000'],
|
||||
loss_type='pointwise_reranker',
|
||||
split_dataset_ratio=0.01,
|
||||
tensor_model_parallel_size=2,
|
||||
recompute_granularity='full',
|
||||
recompute_method='uniform',
|
||||
recompute_num_layers=1,
|
||||
train_iters=100,
|
||||
eval_iters=5,
|
||||
save_steps=5,
|
||||
no_save_optim=True,
|
||||
no_save_rng=True,
|
||||
sequence_parallel=True,
|
||||
finetune=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_embedding()
|
||||
# test_reranker()
|
||||
@@ -0,0 +1,65 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def _infer_model(engine, system=None, messages=None):
|
||||
from swift.infer_engine import RequestConfig
|
||||
from swift.utils import get_logger, seed_everything
|
||||
logger = get_logger()
|
||||
seed_everything(42)
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0)
|
||||
if messages is None:
|
||||
messages = []
|
||||
if system is not None:
|
||||
messages += [{'role': 'system', 'content': system}]
|
||||
messages += [{'role': 'user', 'content': 'who are you?'}]
|
||||
resp = engine.infer([{'messages': messages}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}, {'role': 'user', 'content': '<image>这是什么'}]
|
||||
else:
|
||||
messages = messages.copy()
|
||||
resp = engine.infer([{
|
||||
'messages': messages,
|
||||
}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}]
|
||||
logger.info(f'model: {engine.model_info.model_name}, messages: {messages}')
|
||||
return response
|
||||
|
||||
|
||||
model_id = 'Qwen/Qwen2-7B-Instruct'
|
||||
|
||||
|
||||
def hf2mcore():
|
||||
from swift import ExportArguments, export_main
|
||||
export_main(
|
||||
ExportArguments(
|
||||
model=model_id, to_mcore=True, torch_dtype='bfloat16', exist_ok=True, test_convert_precision=True))
|
||||
|
||||
|
||||
def mcore2hf():
|
||||
from swift import ExportArguments, export_main
|
||||
export_main(
|
||||
ExportArguments(
|
||||
mcore_model='Qwen2-7B-Instruct-mcore',
|
||||
to_hf=True,
|
||||
torch_dtype='bfloat16',
|
||||
exist_ok=True,
|
||||
test_convert_precision=True))
|
||||
|
||||
|
||||
def infer_hf_align():
|
||||
from swift.infer_engine import TransformersEngine
|
||||
engine = TransformersEngine(model_id)
|
||||
response = _infer_model(engine)
|
||||
engine = TransformersEngine('Qwen2-7B-Instruct-mcore-hf')
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# hf2mcore()
|
||||
mcore2hf()
|
||||
infer_hf_align()
|
||||
@@ -0,0 +1,96 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
if __name__ == '__main__':
|
||||
from swift.megatron import MegatronRLHFArguments, megatron_rlhf_main
|
||||
megatron_rlhf_main(
|
||||
MegatronRLHFArguments(
|
||||
rlhf_type='gkd',
|
||||
model='Qwen/Qwen3-4B-Base',
|
||||
teacher_model='Qwen/Qwen3-8B',
|
||||
tuner_type='lora',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-en#2000', 'AI-ModelScope/alpaca-gpt4-data-zh#2000'],
|
||||
tensor_model_parallel_size=2,
|
||||
seq_kd=False,
|
||||
lmbda=1,
|
||||
beta=1,
|
||||
micro_batch_size=2,
|
||||
global_batch_size=16,
|
||||
num_train_epochs=1,
|
||||
lr=5e-6,
|
||||
logging_steps=1,
|
||||
max_length=2048,
|
||||
max_completion_length=1024,
|
||||
attention_backend='flash',
|
||||
use_vllm=True,
|
||||
vllm_mode='colocate',
|
||||
vllm_gpu_memory_utilization=0.5,
|
||||
vllm_tensor_parallel_size=1,
|
||||
vllm_max_model_len=16384,
|
||||
sleep_level=1,
|
||||
offload_teacher_model=True,
|
||||
recompute_granularity='full',
|
||||
recompute_method='uniform',
|
||||
recompute_num_layers=1,
|
||||
finetune=True,
|
||||
no_save_optim=True,
|
||||
no_save_rng=True,
|
||||
temperature=1,
|
||||
padding_free=True,
|
||||
sequence_parallel=True,
|
||||
))
|
||||
|
||||
|
||||
def test_gkd_multi_turn():
|
||||
"""Megatron GKD multi-turn smoke test.
|
||||
|
||||
Verifies that ``_prepare_scheduler`` (now in MegatronRolloutMixin) initializes
|
||||
the multi_turn_scheduler for GKD, and that multi-turn rollout → encode → JSD
|
||||
loss completes without error.
|
||||
"""
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
from swift.megatron import MegatronRLHFArguments, megatron_rlhf_main
|
||||
megatron_rlhf_main(
|
||||
MegatronRLHFArguments(
|
||||
rlhf_type='gkd',
|
||||
model='Qwen/Qwen3-4B',
|
||||
teacher_model='Qwen/Qwen3-8B',
|
||||
tuner_type='lora',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-en#200'],
|
||||
tensor_model_parallel_size=2,
|
||||
seq_kd=False,
|
||||
lmbda=1,
|
||||
beta=1,
|
||||
micro_batch_size=2,
|
||||
global_batch_size=8,
|
||||
num_train_epochs=1,
|
||||
lr=5e-6,
|
||||
logging_steps=1,
|
||||
max_length=2048,
|
||||
max_completion_length=512,
|
||||
attention_backend='flash',
|
||||
use_vllm=True,
|
||||
vllm_mode='colocate',
|
||||
vllm_gpu_memory_utilization=0.5,
|
||||
vllm_tensor_parallel_size=1,
|
||||
vllm_max_model_len=4096,
|
||||
sleep_level=1,
|
||||
offload_teacher_model=True,
|
||||
recompute_granularity='full',
|
||||
recompute_method='uniform',
|
||||
recompute_num_layers=1,
|
||||
finetune=True,
|
||||
no_save_optim=True,
|
||||
no_save_rng=True,
|
||||
temperature=1,
|
||||
padding_free=True,
|
||||
sequence_parallel=True,
|
||||
multi_turn_scheduler='math_tip_trick',
|
||||
max_turns=2,
|
||||
))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_gkd_multi_turn()
|
||||
@@ -0,0 +1,62 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['MAX_PIXELS'] = '602112'
|
||||
|
||||
if __name__ == '__main__':
|
||||
from swift.megatron import MegatronRLHFArguments, megatron_rlhf_main
|
||||
megatron_rlhf_main(
|
||||
MegatronRLHFArguments(
|
||||
rlhf_type='grpo',
|
||||
model='Qwen/Qwen3.5-4B',
|
||||
save_safetensors=True,
|
||||
context_parallel_size=1,
|
||||
tuner_type='lora',
|
||||
tensor_model_parallel_size=2,
|
||||
dataset=['AI-ModelScope/clevr_cogen_a_train#10000'],
|
||||
num_train_epochs=1,
|
||||
global_batch_size=128,
|
||||
vllm_mm_processor_cache_gb=0,
|
||||
micro_batch_size=4,
|
||||
steps_per_generation=4,
|
||||
num_generations=8,
|
||||
external_plugins=['examples/train/grpo/plugin/plugin.py'],
|
||||
reward_funcs=['external_r1v_acc', 'format'],
|
||||
use_vllm=True,
|
||||
vllm_mode='colocate',
|
||||
vllm_gpu_memory_utilization=0.5,
|
||||
vllm_max_model_len=8192,
|
||||
max_length=8192,
|
||||
max_completion_length=2048,
|
||||
lr=1e-4,
|
||||
bf16=True,
|
||||
beta=0.001,
|
||||
importance_sampling_level='token',
|
||||
epsilon=0.2,
|
||||
epsilon_high=0.2,
|
||||
dynamic_sample=True,
|
||||
overlong_filter=True,
|
||||
loss_type='grpo',
|
||||
sleep_level=2,
|
||||
offload_model=True,
|
||||
offload_bridge=False,
|
||||
offload_optimizer=True,
|
||||
logging_steps=1,
|
||||
recompute_granularity='full',
|
||||
recompute_method='uniform',
|
||||
recompute_num_layers=1,
|
||||
finetune=True,
|
||||
dataloader_num_workers=4,
|
||||
dataset_num_proc=4,
|
||||
no_save_optim=True,
|
||||
no_save_rng=True,
|
||||
attention_backend='flash',
|
||||
temperature=1,
|
||||
system='examples/train/grpo/prompt.txt',
|
||||
padding_free=True,
|
||||
log_completions=True,
|
||||
train_iters=100,
|
||||
eval_steps=1000,
|
||||
save_steps=1000,
|
||||
))
|
||||
@@ -0,0 +1,38 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
|
||||
|
||||
def test_kto():
|
||||
from swift.megatron import MegatronRLHFArguments, megatron_rlhf_main
|
||||
megatron_rlhf_main(
|
||||
MegatronRLHFArguments(
|
||||
model='Qwen/Qwen2.5-7B-Instruct',
|
||||
rlhf_type='kto',
|
||||
tuner_type='lora',
|
||||
load_from_cache_file=True,
|
||||
dataset=['AI-ModelScope/ultrafeedback-binarized-preferences-cleaned-kto#10000'],
|
||||
target_modules=['all-linear'],
|
||||
tensor_model_parallel_size=2,
|
||||
split_dataset_ratio=0.01,
|
||||
micro_batch_size=4,
|
||||
global_batch_size=16,
|
||||
recompute_granularity='full',
|
||||
recompute_method='uniform',
|
||||
recompute_num_layers=1,
|
||||
eval_steps=10,
|
||||
save_steps=10,
|
||||
logging_steps=1,
|
||||
finetune=True,
|
||||
num_train_epochs=1,
|
||||
max_length=2048,
|
||||
packing=True,
|
||||
dataset_num_proc=8,
|
||||
cross_entropy_loss_fusion=True,
|
||||
sequence_parallel=True,
|
||||
))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_kto()
|
||||
@@ -0,0 +1,80 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
|
||||
|
||||
def test_sft():
|
||||
from swift.megatron import MegatronSftArguments, megatron_sft_main
|
||||
megatron_sft_main(
|
||||
MegatronSftArguments(
|
||||
mcore_model='Qwen2.5-3B-Instruct-mcore',
|
||||
dataset=['AI-ModelScope/function-calling-chatml#10000'],
|
||||
loss_scale='hermes',
|
||||
split_dataset_ratio=0.01,
|
||||
tensor_model_parallel_size=2,
|
||||
tuner_type='lora',
|
||||
recompute_granularity='full',
|
||||
recompute_method='uniform',
|
||||
recompute_num_layers=1,
|
||||
# pipeline_model_parallel_size=2,
|
||||
# freeze_parameters_ratio=0.5,
|
||||
train_iters=100,
|
||||
modules_to_save=['word_embeddings', 'output_layer'],
|
||||
eval_iters=5,
|
||||
save_steps=5,
|
||||
no_save_optim=True,
|
||||
no_save_rng=True,
|
||||
sequence_parallel=True,
|
||||
finetune=True))
|
||||
|
||||
|
||||
def test_moe():
|
||||
from swift.megatron import MegatronSftArguments, megatron_sft_main
|
||||
megatron_sft_main(
|
||||
MegatronSftArguments(
|
||||
mcore_model='Qwen1.5-MoE-A2.7B-mcore',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#5000'],
|
||||
split_dataset_ratio=0.01,
|
||||
moe_shared_expert_overlap=True,
|
||||
moe_grouped_gemm=True,
|
||||
tensor_model_parallel_size=2,
|
||||
# expert_model_parallel_size=2,
|
||||
tuner_type='lora',
|
||||
recompute_granularity='full',
|
||||
modules_to_save=['word_embeddings', 'output_layer'],
|
||||
recompute_method='uniform',
|
||||
recompute_num_layers=1,
|
||||
# pipeline_model_parallel_size=2,
|
||||
# freeze_parameters_ratio=0.5,
|
||||
train_iters=100,
|
||||
eval_iters=5,
|
||||
save_steps=5,
|
||||
no_save_optim=True,
|
||||
no_save_rng=True,
|
||||
sequence_parallel=True,
|
||||
finetune=True))
|
||||
|
||||
|
||||
def test_convert():
|
||||
from swift import ExportArguments, export_main
|
||||
export_main(
|
||||
ExportArguments(
|
||||
mcore_adapter='megatron_output/vx-xxx/checkpoint-xxx',
|
||||
to_hf=True,
|
||||
test_convert_precision=True,
|
||||
))
|
||||
|
||||
|
||||
def test_embedding():
|
||||
pass
|
||||
|
||||
|
||||
def test_resume():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_sft()
|
||||
# test_moe()
|
||||
# test_convert()
|
||||
@@ -0,0 +1,92 @@
|
||||
import unittest
|
||||
|
||||
|
||||
class TestMegatronArgs(unittest.TestCase):
|
||||
"""Megatron import / args smoke test (GPU and NPU adapted).
|
||||
|
||||
Covers: MegatronSftArguments initialization, MegatronRLHFArguments,
|
||||
MegatronArguments field validation.
|
||||
|
||||
Why these tests are needed:
|
||||
- tests/megatron/test_train.py and test_lora.py have top-level functions
|
||||
that require multi-GPU and mcore models, too heavy for CI.
|
||||
- Megatron argument construction is a common entry point that should be
|
||||
validated even without a full training run.
|
||||
- On NPU, Megatron dependencies (mcore, MindSpeed) may not be installed,
|
||||
so we gracefully skip.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
try:
|
||||
from swift.megatron import (MegatronArguments, MegatronExportArguments, MegatronPretrainArguments,
|
||||
MegatronRLHFArguments, MegatronSftArguments)
|
||||
cls._megatron_available = True
|
||||
cls.MegatronArguments = MegatronArguments
|
||||
cls.MegatronSftArguments = MegatronSftArguments
|
||||
cls.MegatronRLHFArguments = MegatronRLHFArguments
|
||||
except (ImportError, RuntimeError) as e:
|
||||
cls._megatron_available = False
|
||||
cls._skip_reason = str(e)
|
||||
|
||||
def _skip_if_no_megatron(self):
|
||||
if not self._megatron_available:
|
||||
self.skipTest(f'Megatron dependencies not available: {self._skip_reason}')
|
||||
|
||||
def test_megatron_import(self):
|
||||
self._skip_if_no_megatron()
|
||||
|
||||
def test_megatron_sft_args_construction(self):
|
||||
self._skip_if_no_megatron()
|
||||
|
||||
args = self.MegatronSftArguments(
|
||||
mcore_model='Qwen2-7B-Instruct-mcore',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#20'],
|
||||
split_dataset_ratio=0.01,
|
||||
tensor_model_parallel_size=1,
|
||||
train_iters=1,
|
||||
skip_megatron_init=True,
|
||||
)
|
||||
self.assertEqual(args.train_iters, 1)
|
||||
self.assertEqual(args.tensor_model_parallel_size, 1)
|
||||
|
||||
def test_megatron_rlhf_args_construction(self):
|
||||
self._skip_if_no_megatron()
|
||||
|
||||
args = self.MegatronRLHFArguments(
|
||||
rlhf_type='grpo',
|
||||
mcore_model='Qwen2-7B-Instruct-mcore',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#20'],
|
||||
reward_funcs=['format'],
|
||||
num_generations=2,
|
||||
max_completion_length=128,
|
||||
tensor_model_parallel_size=1,
|
||||
train_iters=1,
|
||||
skip_megatron_init=True,
|
||||
)
|
||||
self.assertEqual(args.rlhf_type, 'grpo')
|
||||
self.assertIn('format', args.reward_funcs)
|
||||
|
||||
def test_megatron_base_args_fields(self):
|
||||
self._skip_if_no_megatron()
|
||||
|
||||
expected_fields = [
|
||||
'tensor_model_parallel_size',
|
||||
'pipeline_model_parallel_size',
|
||||
'context_parallel_size',
|
||||
'sequence_parallel_size',
|
||||
'train_iters',
|
||||
'micro_batch_size',
|
||||
'global_batch_size',
|
||||
'lr',
|
||||
'min_lr',
|
||||
'bf16',
|
||||
]
|
||||
from dataclasses import fields
|
||||
field_names = {f.name for f in fields(self.MegatronArguments)}
|
||||
for field_name in expected_fields:
|
||||
self.assertIn(field_name, field_names, f'MegatronArguments missing field: {field_name}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,45 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
|
||||
|
||||
if __name__ == '__main__':
|
||||
from swift.megatron import MegatronRLHFArguments, megatron_rlhf_main
|
||||
megatron_rlhf_main(
|
||||
MegatronRLHFArguments(
|
||||
rlhf_type='gkd',
|
||||
model='Qwen/Qwen3-4B',
|
||||
teacher_model='Qwen/Qwen3-4B',
|
||||
external_plugins=['examples/train/rlhf/opsd/opsd_plugin.py'],
|
||||
dataset=['open-r1/OpenThoughts-114k-math'],
|
||||
use_vllm=True,
|
||||
vllm_mode='colocate',
|
||||
vllm_gpu_memory_utilization=0.6,
|
||||
vllm_max_model_len=10240,
|
||||
tuner_type='lora',
|
||||
lora_rank=64,
|
||||
lora_alpha=128,
|
||||
sleep_level=1,
|
||||
lmbda=1.0,
|
||||
beta=0.5,
|
||||
temperature=1.2,
|
||||
sft_alpha=0,
|
||||
torch_dtype='bfloat16',
|
||||
micro_batch_size=2,
|
||||
global_batch_size=32,
|
||||
train_iters=1000,
|
||||
lr=2e-5,
|
||||
save_steps=100,
|
||||
save_total_limit=10,
|
||||
logging_steps=1,
|
||||
max_length=8192,
|
||||
max_completion_length=2048,
|
||||
tensor_model_parallel_size=1,
|
||||
pipeline_model_parallel_size=1,
|
||||
attention_backend='flash',
|
||||
recompute_granularity='selective',
|
||||
finetune=True,
|
||||
no_save_optim=True,
|
||||
no_save_rng=True,
|
||||
))
|
||||
@@ -0,0 +1,404 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import torch
|
||||
from types import SimpleNamespace
|
||||
|
||||
from swift.ray.megatron.gkd_trainer import GKDTrainer
|
||||
from swift.ray.megatron.megatron_worker import MegatronWorker
|
||||
from swift.rlhf_trainers.gkd_loss import TeacherOutput, extract_active
|
||||
|
||||
_collate = MegatronWorker._collate_teacher_outputs
|
||||
|
||||
|
||||
def _topk_to(seq_len, k, fill):
|
||||
"""A per-sample teacher topk tensor shaped [1, seq_len, k]."""
|
||||
return TeacherOutput(
|
||||
topk_logprobs=torch.full((1, seq_len, k), float(fill)),
|
||||
topk_indices=torch.zeros((1, seq_len, k), dtype=torch.long),
|
||||
)
|
||||
|
||||
|
||||
def test_collate_padding_free_concat_and_offbyone_pad():
|
||||
"""padding_free: concat per-sample along seq dim, then pad to target_seq_len.
|
||||
|
||||
This is the off-by-one fix: the student collation pads the concatenated
|
||||
sequence to a multiple via get_padding_to (SP), so the teacher (built from raw
|
||||
per-sample lengths) can be a few tokens short and must be padded to match.
|
||||
"""
|
||||
k = 4
|
||||
samples = [_topk_to(3, k, -1.0), _topk_to(5, k, -2.0)] # raw total = 8
|
||||
target = 10 # student SP-padded length (8 -> 10)
|
||||
out = _collate(samples, device='cpu', padding_free=True, target_seq_len=target)
|
||||
assert out.topk_logprobs.shape == (1, target, k), out.topk_logprobs.shape
|
||||
assert out.topk_indices.shape == (1, target, k)
|
||||
# real tokens 0..7 keep their values; padded tail 8..9 is -inf / 0
|
||||
assert torch.isinf(out.topk_logprobs[0, 8:, :]).all()
|
||||
assert (out.topk_indices[0, 8:, :] == 0).all()
|
||||
assert not torch.isinf(out.topk_logprobs[0, :8, :]).any()
|
||||
|
||||
|
||||
def test_collate_padding_free_offbyone_single_token():
|
||||
"""The exact failure mode that deadlocked T3: total length odd, padded +1."""
|
||||
k = 2
|
||||
samples = [_topk_to(8277, k, -1.0)] # one micro-batch sample, raw len 8277 (odd)
|
||||
out = _collate(samples, device='cpu', padding_free=True, target_seq_len=8278)
|
||||
assert out.topk_logprobs.shape == (1, 8278, k)
|
||||
assert torch.isinf(out.topk_logprobs[0, 8277:, :]).all()
|
||||
|
||||
|
||||
def test_collate_padding_free_drops_empty_placeholder():
|
||||
"""colocated path emits [1, full, k] for sample 0 and empty [0, ...] for the
|
||||
rest of a micro-batch; empties must be dropped before the seq-dim concat."""
|
||||
k = 3
|
||||
full = _topk_to(6, k, -1.0)
|
||||
empty = TeacherOutput(
|
||||
topk_logprobs=torch.full((0, 6, k), float('-inf')),
|
||||
topk_indices=torch.zeros((0, 6, k), dtype=torch.long),
|
||||
)
|
||||
out = _collate([full, empty], device='cpu', padding_free=True, target_seq_len=6)
|
||||
assert out.topk_logprobs.shape == (1, 6, k)
|
||||
|
||||
|
||||
def test_collate_non_padding_free_stacks_on_batch_dim():
|
||||
"""non padding_free: per-sample tensors padded to target then stacked on dim 0."""
|
||||
k = 4
|
||||
samples = [_topk_to(3, k, -1.0), _topk_to(5, k, -2.0)]
|
||||
out = _collate(samples, device='cpu', padding_free=False, target_seq_len=5)
|
||||
assert out.topk_logprobs.shape == (2, 5, k), out.topk_logprobs.shape
|
||||
# sample 0 padded from 3 -> 5
|
||||
assert torch.isinf(out.topk_logprobs[0, 3:, :]).all()
|
||||
assert not torch.isinf(out.topk_logprobs[1]).any()
|
||||
|
||||
|
||||
def test_collate_opsd_keeps_teacher_length_not_student_target():
|
||||
"""OPSD: teacher scores a different prompt, so its length differs from the
|
||||
student. The collation must KEEP the teacher length (ignore target_seq_len)
|
||||
and concat labels (extract_active aligns by mask, not position)."""
|
||||
k = 3
|
||||
t_total = 7 # teacher (opsd) sequence length
|
||||
full = TeacherOutput(
|
||||
topk_logprobs=torch.full((1, t_total, k), -1.0),
|
||||
topk_indices=torch.zeros((1, t_total, k), dtype=torch.long),
|
||||
labels=torch.full((1, t_total), 5, dtype=torch.long),
|
||||
)
|
||||
empty = TeacherOutput() # padding_free placeholder for the rest of the micro-batch
|
||||
# target_seq_len is the *student* length (e.g. 12) — must be ignored for OPSD.
|
||||
out = _collate([full, empty], device='cpu', padding_free=True, target_seq_len=12, is_opsd=True)
|
||||
assert out.topk_logprobs.shape == (1, t_total, k), out.topk_logprobs.shape
|
||||
assert out.labels.shape == (1, t_total)
|
||||
assert (out.labels == 5).all()
|
||||
|
||||
|
||||
def test_build_per_sample_teacher_output_uses_raw_input_length():
|
||||
"""Standalone teacher outputs are built from each sample's RAW (un-CP-padded) input
|
||||
length. Because these raw per-sample token-logprobs cannot be CP-sharded to match the
|
||||
student, CP>1 with standalone teacher replicas is rejected by a fail-fast in
|
||||
GKDTrainer._collate_for_workers_gkd (use a colocated teacher_model for CP>1).
|
||||
This test guards the raw-length contract that the CP>1 fail-fast depends on.
|
||||
"""
|
||||
k = 3
|
||||
raw_len = 5
|
||||
lps = [[-1.0] * k for _ in range(raw_len)]
|
||||
ixs = [[0] * k for _ in range(raw_len)]
|
||||
encoded = {'input_ids': list(range(raw_len))}
|
||||
out = GKDTrainer._build_per_sample_teacher_output((lps, ixs), encoded, topk=k)
|
||||
assert out.topk_logprobs.shape == (1, raw_len, k), out.topk_logprobs.shape
|
||||
assert out.topk_indices.shape == (1, raw_len, k)
|
||||
assert out.labels is None
|
||||
|
||||
|
||||
def test_extract_active_opsd_aligns_by_mask_across_lengths():
|
||||
"""OPSD: teacher and student have different sequence lengths; extract_active
|
||||
selects response positions by their own masks and requires equal counts."""
|
||||
V, k = 8, 3
|
||||
# student: length 5, 2 response positions (indices 3,4)
|
||||
s_logits = torch.randn(1, 5, V)
|
||||
s_labels = torch.tensor([[-100, -100, -100, 1, 2]])
|
||||
# teacher (opsd): length 7, 2 response positions (indices 5,6) — same count
|
||||
t = TeacherOutput(
|
||||
topk_logprobs=torch.randn(1, 7, k),
|
||||
topk_indices=torch.zeros((1, 7, k), dtype=torch.long),
|
||||
labels=torch.tensor([[-100, -100, -100, -100, -100, 1, 2]]),
|
||||
)
|
||||
s_act, t_act, n = extract_active(s_logits, t, s_labels)
|
||||
assert int(n) == 2
|
||||
assert s_act.shape == (2, V)
|
||||
assert t_act.topk_logprobs.shape == (2, k)
|
||||
|
||||
|
||||
def test_extract_active_opsd_count_mismatch_raises():
|
||||
s_logits = torch.randn(1, 5, 8)
|
||||
s_labels = torch.tensor([[-100, -100, -100, 1, 2]]) # 2 response tokens
|
||||
t = TeacherOutput(
|
||||
topk_logprobs=torch.randn(1, 6, 3),
|
||||
topk_indices=torch.zeros((1, 6, 3), dtype=torch.long),
|
||||
labels=torch.tensor([[-100, -100, -100, -100, -100, 9]]), # 1 token
|
||||
)
|
||||
try:
|
||||
extract_active(s_logits, t, s_labels)
|
||||
raise AssertionError('expected an assertion on OPSD count mismatch')
|
||||
except AssertionError as e:
|
||||
assert 'OPSD' in str(e) or 'mismatch' in str(e) or 'count' in str(e).lower()
|
||||
|
||||
|
||||
def test_extract_active_non_opsd_uses_student_labels():
|
||||
"""Non-OPSD: teacher_output.labels is None (Ray GKD non-OPSD path).
|
||||
|
||||
The student label mask should apply to both student and teacher.
|
||||
This is the Critical #1 regression test: before the fix, extract_active
|
||||
crashed with TypeError on ``None != -100``.
|
||||
"""
|
||||
V, k = 8, 3
|
||||
# student: length 5, 3 response positions (indices 2,3,4)
|
||||
s_logits = torch.randn(1, 5, V)
|
||||
s_labels = torch.tensor([[-100, -100, 1, 2, 3]])
|
||||
# teacher (non-OPSD): same length, labels=None
|
||||
t = TeacherOutput(
|
||||
topk_logprobs=torch.randn(1, 5, k),
|
||||
topk_indices=torch.zeros((1, 5, k), dtype=torch.long),
|
||||
labels=None,
|
||||
)
|
||||
s_act, t_act, n = extract_active(s_logits, t, s_labels)
|
||||
assert int(n) == 3
|
||||
assert s_act.shape == (3, V)
|
||||
assert t_act.topk_logprobs.shape == (3, k)
|
||||
|
||||
|
||||
def test_extract_active_non_opsd_full_logits():
|
||||
"""Non-OPSD with full-vocab teacher (no topk): labels=None path.
|
||||
|
||||
Verifies that the student mask is used for both student and teacher
|
||||
when teacher_output.labels is None.
|
||||
"""
|
||||
V = 8
|
||||
s_logits = torch.randn(1, 4, V)
|
||||
s_labels = torch.tensor([[-100, 1, 2, 3]])
|
||||
t = TeacherOutput(
|
||||
full_logits=torch.randn(1, 4, V),
|
||||
labels=None,
|
||||
)
|
||||
s_act, t_act, n = extract_active(s_logits, t, s_labels)
|
||||
assert int(n) == 3
|
||||
assert s_act.shape == (3, V)
|
||||
assert t_act.full_logits.shape == (3, V)
|
||||
|
||||
|
||||
def test_megatron_assemble_teacher_outputs_api_topk_rolls_labels():
|
||||
"""Megatron Teacher API + topk: ``_assemble_teacher_outputs`` must roll teacher
|
||||
labels by -1 so the invariant 'teacher_output.labels is pre-shifted before
|
||||
extract_active' holds on the API path too (the local-teacher path gets shifted
|
||||
labels from _prepare_batch). assemble_teacher_output returns the RAW labels, so
|
||||
the trainer applies the shift; without it the API path would feed unshifted
|
||||
teacher labels against shifted student labels -> silent KL/JSD misalignment.
|
||||
"""
|
||||
try:
|
||||
from swift.megatron.trainers.gkd_trainer import MegatronGKDTrainer
|
||||
except Exception as e: # noqa: megatron-core not installed in this env
|
||||
print(f'SKIP test_megatron_assemble_teacher_outputs_api_topk_rolls_labels: {e}')
|
||||
return
|
||||
|
||||
k = 3
|
||||
# raw (unshifted) labels: prompt=-100, response at positions 2,3,4
|
||||
raw_labels = torch.tensor([[-100, -100, 11, 22, 33]])
|
||||
seq_len = raw_labels.shape[-1]
|
||||
# parsed teacher topk: one (logprobs, indices) row per response token (len+1 cu)
|
||||
parsed = [([[-1.0] * k] * (seq_len - 1), [[0] * k] * (seq_len - 1))]
|
||||
teacher_model_inputs = {
|
||||
'input_ids': torch.zeros((1, seq_len), dtype=torch.long),
|
||||
'labels': raw_labels.clone(),
|
||||
'attention_mask': torch.ones((1, seq_len), dtype=torch.long),
|
||||
}
|
||||
encoded_batch = {'_teacher_parsed': parsed, 'teacher_model_inputs': teacher_model_inputs}
|
||||
|
||||
stub = SimpleNamespace(gkd_logits_topk=k, template=SimpleNamespace(padding_free=False), device=torch.device('cpu'))
|
||||
MegatronGKDTrainer._assemble_teacher_outputs(stub, [encoded_batch])
|
||||
|
||||
teacher_out = encoded_batch['teacher_output']
|
||||
assert torch.equal(teacher_out.labels, torch.roll(raw_labels, shifts=-1, dims=-1))
|
||||
assert teacher_out.topk_logprobs.shape == (1, seq_len, k)
|
||||
|
||||
# The shifted teacher labels must align with shifted student labels in extract_active.
|
||||
s_labels = torch.roll(raw_labels, shifts=-1, dims=-1)
|
||||
s_logits = torch.randn(1, seq_len, 8)
|
||||
s_act, t_act, n = extract_active(s_logits, teacher_out, s_labels)
|
||||
assert int(n) == 3
|
||||
assert t_act.topk_logprobs.shape == (3, k)
|
||||
|
||||
|
||||
def test_example_yaml_config_contracts():
|
||||
"""Config-contract regression for the standardized example yamls.
|
||||
|
||||
- teacher replicas (standalone) must declare vllm_engine_kwargs.max_logprobs
|
||||
>= gkd_logits_topk, else vLLM rejects the prompt_logprobs request.
|
||||
- the standalone teacher group serves a real teacher checkpoint (model override).
|
||||
"""
|
||||
import os
|
||||
import yaml
|
||||
base = os.path.join(os.path.dirname(__file__), '..', '..', 'examples', 'ray', 'gkd')
|
||||
|
||||
cfg = yaml.safe_load(open(os.path.join(base, 'rollout_colocate_teacher_standalone.yaml')))
|
||||
topk = cfg['gkd_logits_topk']
|
||||
teacher = cfg['teacher']
|
||||
max_logprobs = teacher['vllm_engine_kwargs']['max_logprobs']
|
||||
assert max_logprobs >= topk, f'teacher max_logprobs {max_logprobs} < gkd_logits_topk {topk}'
|
||||
assert teacher.get('model'), 'standalone teacher group must override `model`'
|
||||
|
||||
# colocate / separate examples keep max_length & max_completion_length consistent
|
||||
for name in ('rollout_colocate_teacher_colocate.yaml', 'rollout_separate_teacher_colocate.yaml'):
|
||||
c = yaml.safe_load(open(os.path.join(base, name)))
|
||||
assert c['max_length'] > 0 and c['max_completion_length'] > 0
|
||||
|
||||
|
||||
def test_ray_gkd_prepare_multi_turn_initializes_scheduler():
|
||||
"""Verify that GKDTrainer._prepare_multi_turn() initializes the scheduler.
|
||||
|
||||
This tests the fix that adds _prepare_multi_turn() to Ray GKD trainer
|
||||
(previously only GRPO had it). We mock the args and check that
|
||||
_multi_turn_scheduler is set correctly.
|
||||
"""
|
||||
from types import SimpleNamespace
|
||||
|
||||
from swift.rollout.multi_turn import MathTipsScheduler
|
||||
|
||||
# Create a minimal mock trainer instance (bypass __init__)
|
||||
trainer = GKDTrainer.__new__(GKDTrainer)
|
||||
trainer.args = SimpleNamespace(
|
||||
multi_turn_scheduler='math_tip_trick',
|
||||
max_turns=2,
|
||||
gym_env=None,
|
||||
)
|
||||
|
||||
# Call _prepare_multi_turn directly
|
||||
trainer._prepare_multi_turn()
|
||||
|
||||
assert trainer._multi_turn_scheduler is not None, 'Scheduler should be initialized'
|
||||
assert isinstance(trainer._multi_turn_scheduler,
|
||||
MathTipsScheduler), (f'Expected MathTipsScheduler, got {type(trainer._multi_turn_scheduler)}')
|
||||
assert trainer._max_turns == 2
|
||||
assert trainer._enable_server_multi_turn is False
|
||||
|
||||
|
||||
def test_ray_gkd_prepare_multi_turn_none_when_not_configured():
|
||||
"""Verify that _prepare_multi_turn() leaves scheduler as None when not configured."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
trainer = GKDTrainer.__new__(GKDTrainer)
|
||||
trainer.args = SimpleNamespace(
|
||||
multi_turn_scheduler=None,
|
||||
max_turns=None,
|
||||
gym_env=None,
|
||||
)
|
||||
|
||||
trainer._prepare_multi_turn()
|
||||
|
||||
assert trainer._multi_turn_scheduler is None
|
||||
assert trainer._enable_server_multi_turn is False
|
||||
|
||||
|
||||
def test_ray_gkd_prepare_multi_turn_unknown_scheduler_raises():
|
||||
"""Unknown scheduler name should raise ValueError."""
|
||||
from types import SimpleNamespace
|
||||
|
||||
trainer = GKDTrainer.__new__(GKDTrainer)
|
||||
trainer.args = SimpleNamespace(
|
||||
multi_turn_scheduler='nonexistent_scheduler',
|
||||
max_turns=3,
|
||||
gym_env=None,
|
||||
)
|
||||
|
||||
try:
|
||||
trainer._prepare_multi_turn()
|
||||
assert False, 'Should have raised ValueError for unknown scheduler'
|
||||
except ValueError as e:
|
||||
assert 'nonexistent_scheduler' in str(e)
|
||||
|
||||
|
||||
def test_ray_gkd_generate_uses_multi_turn_scheduler():
|
||||
"""Verify that _generate() dispatches to run_multi_turn when scheduler is set.
|
||||
|
||||
We mock _distribute_to_replicas to return canned responses, then check
|
||||
that the output structure matches multi-turn format (response_token_ids
|
||||
accumulated across turns).
|
||||
"""
|
||||
from types import SimpleNamespace
|
||||
|
||||
from swift.infer_engine.protocol import ChatCompletionResponse, ChatCompletionResponseChoice, Message
|
||||
from swift.rollout.multi_turn import MathTipsScheduler
|
||||
|
||||
# Create a minimal mock trainer
|
||||
trainer = GKDTrainer.__new__(GKDTrainer)
|
||||
trainer.args = SimpleNamespace(
|
||||
max_completion_length=128,
|
||||
temperature=1.0,
|
||||
top_p=1.0,
|
||||
top_k=80,
|
||||
stop_words=[],
|
||||
)
|
||||
trainer._multi_turn_scheduler = None # start with no scheduler
|
||||
trainer._enable_server_multi_turn = False
|
||||
trainer._max_turns = 1
|
||||
|
||||
# Mock _distribute_to_replicas to return canned responses
|
||||
call_count = [0]
|
||||
|
||||
def mock_distribute(requests, request_config):
|
||||
call_count[0] += 1
|
||||
responses = []
|
||||
for req in requests:
|
||||
choice = ChatCompletionResponseChoice(
|
||||
index=0,
|
||||
message=Message(role='assistant', content='The answer is 4.'),
|
||||
finish_reason='stop',
|
||||
token_ids=[1, 2, 3, 4, 5],
|
||||
)
|
||||
resp = ChatCompletionResponse(choices=[choice])
|
||||
responses.append(resp)
|
||||
return responses
|
||||
|
||||
trainer._distribute_to_replicas = mock_distribute
|
||||
|
||||
# Test 1: Without scheduler (single-turn path)
|
||||
from swift.rl_core.data import GKDSample
|
||||
sample = GKDSample(messages=[{'role': 'user', 'content': 'What is 2+2?'}])
|
||||
outputs = trainer._generate([sample])
|
||||
assert len(outputs) == 1
|
||||
assert call_count[0] == 1 # single call
|
||||
|
||||
# Test 2: With scheduler (multi-turn path)
|
||||
# Use a scheduler that always finishes after 1 turn (so we don't loop forever)
|
||||
trainer._multi_turn_scheduler = MathTipsScheduler(max_turns=1)
|
||||
# MathTipsScheduler needs solution in data_dict, mock it
|
||||
sample2 = GKDSample(messages=[{'role': 'user', 'content': 'What is 2+2?'}])
|
||||
sample2.extra['solution'] = '4'
|
||||
sample2.request_id = 'test-req-1'
|
||||
call_count[0] = 0 # reset
|
||||
|
||||
# The scheduler's infer_engine is None; we need to mock the inference
|
||||
# Instead, verify that the multi-turn path is taken by checking call_count
|
||||
# We mock on_trajectory_start to be a no-op
|
||||
import asyncio
|
||||
trainer._multi_turn_scheduler.on_trajectory_start = lambda reqs: asyncio.coroutine(lambda: None)()
|
||||
|
||||
try:
|
||||
outputs = trainer._generate([sample2])
|
||||
# Multi-turn path should have called _distribute_to_replicas at least once
|
||||
assert call_count[0] >= 1, f'Expected at least 1 call, got {call_count[0]}'
|
||||
except Exception:
|
||||
# Multi-turn with mock may fail in scheduler.step(), but the key assertion
|
||||
# is that _distribute_to_replicas was called (multi-turn path was taken)
|
||||
assert call_count[0] >= 1, f'Multi-turn path not taken, call_count={call_count[0]}'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
fns = [v for k, v in sorted(globals().items()) if k.startswith('test_') and callable(v)]
|
||||
failed = 0
|
||||
for fn in fns:
|
||||
try:
|
||||
fn()
|
||||
print(f'PASS {fn.__name__}')
|
||||
except Exception as e: # noqa
|
||||
failed += 1
|
||||
import traceback
|
||||
print(f'FAIL {fn.__name__}: {e}')
|
||||
traceback.print_exc()
|
||||
print(f'\n{len(fns) - failed}/{len(fns)} passed')
|
||||
raise SystemExit(1 if failed else 0)
|
||||
@@ -0,0 +1,40 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
|
||||
|
||||
def test_dpo():
|
||||
from swift.megatron import MegatronRLHFArguments, megatron_rlhf_main
|
||||
megatron_rlhf_main(
|
||||
MegatronRLHFArguments(
|
||||
mcore_model='Qwen2.5-3B-Instruct-mcore',
|
||||
dataset=['hjh0119/shareAI-Llama3-DPO-zh-en-emoji#10000'],
|
||||
split_dataset_ratio=0.01,
|
||||
micro_batch_size=16,
|
||||
tensor_model_parallel_size=2,
|
||||
eval_steps=5,
|
||||
logging_steps=1,
|
||||
finetune=True,
|
||||
num_train_epochs=1,
|
||||
))
|
||||
|
||||
|
||||
def test_hf():
|
||||
from swift import RLHFArguments, rlhf_main
|
||||
rlhf_main(
|
||||
RLHFArguments(
|
||||
model='Qwen/Qwen2.5-3B-Instruct',
|
||||
dataset=['hjh0119/shareAI-Llama3-DPO-zh-en-emoji#1000'],
|
||||
split_dataset_ratio=0.01,
|
||||
max_steps=100,
|
||||
padding_free=True,
|
||||
attn_impl='flash_attn',
|
||||
train_dataloader_shuffle=False,
|
||||
use_logits_to_keep=False,
|
||||
))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_dpo()
|
||||
# test_hf()
|
||||
@@ -0,0 +1,40 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
|
||||
|
||||
def test_sft():
|
||||
from swift.megatron import MegatronSftArguments, megatron_sft_main
|
||||
megatron_sft_main(
|
||||
MegatronSftArguments(
|
||||
mcore_model='Qwen2-7B-Instruct-mcore',
|
||||
dataset=[
|
||||
'AI-ModelScope/alpaca-gpt4-data-zh#500', 'swift/self-cognition#500',
|
||||
'AI-ModelScope/alpaca-gpt4-data-en#500'
|
||||
],
|
||||
split_dataset_ratio=0.01,
|
||||
tensor_model_parallel_size=2,
|
||||
train_iters=100,
|
||||
model_author=['swift'],
|
||||
model_name=['swift-robot'],
|
||||
sequence_parallel=True,
|
||||
finetune=True))
|
||||
|
||||
|
||||
def test_pt():
|
||||
from swift.megatron import MegatronPretrainArguments, megatron_pretrain_main
|
||||
megatron_pretrain_main(
|
||||
MegatronPretrainArguments(
|
||||
mcore_model='Qwen2-7B-mcore',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#500', 'AI-ModelScope/alpaca-gpt4-data-en#500'],
|
||||
split_dataset_ratio=0.01,
|
||||
tensor_model_parallel_size=2,
|
||||
train_iters=200,
|
||||
eval_iters=5,
|
||||
finetune=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_sft()
|
||||
# test_pt()
|
||||
@@ -0,0 +1,171 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
|
||||
from swift.version import __version__
|
||||
|
||||
|
||||
# 打标
|
||||
class ModelTag(object):
|
||||
_URL = os.environ.get('MODEL_TAG_URL', None)
|
||||
|
||||
# 模型测试结果
|
||||
BATCH_COMMIT_RESULT_URL = f'{_URL}/batchCommitResult'
|
||||
# 测试阶段完成
|
||||
BATCH_REFRESH_STAGE_URL = f'{_URL}/batchRefreshStage'
|
||||
# query_model_stage
|
||||
QUERY_MODEL_STAGE_URL = f'{_URL}/queryModelStage'
|
||||
|
||||
HEADER = {'Content-Type': 'application/json'}
|
||||
|
||||
# 检测结果
|
||||
MODEL_SKIP = 0
|
||||
MODEL_FAIL = 1
|
||||
MODEL_PASS = 2
|
||||
|
||||
class ItemResult(object):
|
||||
|
||||
def __init__(self):
|
||||
self.result = 0
|
||||
self.name = ''
|
||||
self.info = ''
|
||||
|
||||
def to_json(self):
|
||||
return {'name': self.name, 'result': self.result, 'info': self.info}
|
||||
|
||||
def __init__(self):
|
||||
self.job_name = ''
|
||||
self.job_id = ''
|
||||
self.model = ''
|
||||
self.sdk_version = ''
|
||||
self.image_version = ''
|
||||
self.domain = ''
|
||||
self.task = ''
|
||||
self.source = ''
|
||||
self.stage = ''
|
||||
# ItemResult list
|
||||
self.item_result = []
|
||||
|
||||
# 发送请求
|
||||
def _post_request(self, url, param):
|
||||
try:
|
||||
logging.info(url + ' query: ' + str(json.dumps(param, ensure_ascii=False)))
|
||||
res = requests.post(url=url, headers=self.HEADER, data=json.dumps(param, ensure_ascii=False).encode('utf8'))
|
||||
if res.status_code == 200:
|
||||
logging.info(f'{url} post结果: ' + res.text)
|
||||
res_json = json.loads(res.text)
|
||||
if int(res_json['errorCode']) == 200:
|
||||
return res_json['content']
|
||||
else:
|
||||
logging.error(res.text)
|
||||
else:
|
||||
logging.error(res.text)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
return None
|
||||
|
||||
# 提交模型测试结果
|
||||
def batch_commit_result(self):
|
||||
try:
|
||||
param = {
|
||||
'sdkVersion':
|
||||
self.sdk_version,
|
||||
'imageVersion':
|
||||
self.image_version,
|
||||
'source':
|
||||
self.source,
|
||||
'jobName':
|
||||
self.job_name,
|
||||
'jobId':
|
||||
self.job_id,
|
||||
'modelList': [{
|
||||
'model': self.model,
|
||||
'domain': self.domain,
|
||||
'task': self.task,
|
||||
'itemResult': self.item_result
|
||||
}]
|
||||
}
|
||||
return self._post_request(self.BATCH_COMMIT_RESULT_URL, param)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
return
|
||||
|
||||
# 测试阶段完成
|
||||
def batch_refresh_stage(self):
|
||||
try:
|
||||
param = {
|
||||
'sdkVersion': self.sdk_version,
|
||||
'imageVersion': self.image_version,
|
||||
'source': self.source,
|
||||
'stage': self.stage,
|
||||
'modelList': [{
|
||||
'model': self.model,
|
||||
'domain': self.domain,
|
||||
'task': self.task
|
||||
}]
|
||||
}
|
||||
return self._post_request(self.BATCH_REFRESH_STAGE_URL, param)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
return
|
||||
|
||||
# 查询模型某个阶段的最新测试结果(只返回单个结果
|
||||
def query_model_stage(self):
|
||||
try:
|
||||
param = {
|
||||
'sdkVersion': self.sdk_version,
|
||||
'model': self.model,
|
||||
'stage': self.stage,
|
||||
'imageVersion': self.image_version
|
||||
}
|
||||
return self._post_request(self.QUERY_MODEL_STAGE_URL, param)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
return None
|
||||
|
||||
# 提交模型UT测试结果
|
||||
"""
|
||||
model_tag = ModelTag()
|
||||
model_tag.model = "XXX"
|
||||
model_tag.sdk_version = "0.3.7"
|
||||
model_tag.domain = "nlp"
|
||||
model_tag.task = "word-segmentation"
|
||||
item = model_tag.ItemResult()
|
||||
item.result = model_tag.MODEL_PASS
|
||||
item.name = "ALL"
|
||||
item.info = ""
|
||||
model_tag.item_result.append(item.to_json())
|
||||
"""
|
||||
|
||||
def commit_ut_result(self):
|
||||
if self._URL is not None and self._URL != '':
|
||||
self.job_name = 'UT'
|
||||
self.source = 'dev'
|
||||
self.stage = 'integration'
|
||||
|
||||
self.batch_commit_result()
|
||||
self.batch_refresh_stage()
|
||||
|
||||
|
||||
def commit_model_ut_result(model_name, ut_result):
|
||||
model_tag = ModelTag()
|
||||
model_tag.model = model_name.replace('damo/', '')
|
||||
model_tag.sdk_version = __version__
|
||||
# model_tag.domain = ""
|
||||
# model_tag.task = ""
|
||||
item = model_tag.ItemResult()
|
||||
item.result = ut_result
|
||||
item.name = 'ALL'
|
||||
item.info = ''
|
||||
model_tag.item_result.append(item.to_json())
|
||||
model_tag.commit_ut_result()
|
||||
@@ -0,0 +1,8 @@
|
||||
from swift.model import get_model_processor
|
||||
|
||||
if __name__ == '__main__':
|
||||
# model, tokenizer = get_model_processor('Qwen/Qwen2-7B-Instruct', attn_impl='flash_attn')
|
||||
# model, tokenizer = get_model_processor('AIDC-AI/Ovis2-2B', attn_impl='flash_attn')
|
||||
# model, tokenizer = get_model_processor('OpenGVLab/InternVL2-2B', attn_impl='flash_attn')
|
||||
model, tokenizer = get_model_processor('Shanghai_AI_Laboratory/internlm3-8b-instruct', attn_impl='flash_attn')
|
||||
print(model)
|
||||
@@ -0,0 +1,17 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
|
||||
|
||||
def test_llama3():
|
||||
from swift import InferArguments, infer_main
|
||||
infer_main(
|
||||
InferArguments(
|
||||
model='LLM-Research/Meta-Llama-3.1-8B-Instruct',
|
||||
max_batch_size=2,
|
||||
val_dataset='AI-ModelScope/alpaca-gpt4-data-en#2'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_llama3()
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def test_cogvlm():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
|
||||
# infer_main(InferArguments(model='ZhipuAI/cogvlm2-video-llama3-chat'))
|
||||
sft_main(
|
||||
SftArguments(
|
||||
model='ZhipuAI/cogvlm2-video-llama3-chat',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#200', 'swift/VideoChatGPT:Generic#200'],
|
||||
split_dataset_ratio=0.01))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_cogvlm()
|
||||
+566
@@ -0,0 +1,566 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import math
|
||||
import os
|
||||
import pandas
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import torch
|
||||
import unittest
|
||||
import yaml
|
||||
from fnmatch import fnmatch
|
||||
from model_tag import ModelTag, commit_model_ut_result
|
||||
from pathlib import Path
|
||||
from test_utils import get_case_model_info
|
||||
from unittest import TextTestResult
|
||||
|
||||
from swift.utils import get_logger
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
def deduplicate_preserve_order(items):
|
||||
return list(dict.fromkeys(items))
|
||||
|
||||
|
||||
def get_available_npu_devices(visible_npus):
|
||||
npu_devices = [device.strip() for device in visible_npus.split(',') if device.strip()]
|
||||
if not npu_devices:
|
||||
return []
|
||||
|
||||
try:
|
||||
import torch_npu # noqa: F401
|
||||
npu_count = torch.npu.device_count() if hasattr(torch, 'npu') and torch.npu.is_available() else 0
|
||||
except Exception as e:
|
||||
logger.warning('Failed to query torch.npu.device_count(): %s' % e)
|
||||
return []
|
||||
|
||||
if npu_count <= 0:
|
||||
logger.warning('ASCEND_RT_VISIBLE_DEVICES=%s, but torch.npu.device_count()=%s' % (visible_npus, npu_count))
|
||||
return []
|
||||
if npu_count < len(npu_devices):
|
||||
logger.warning('ASCEND_RT_VISIBLE_DEVICES=%s, but torch.npu.device_count()=%s; using %s' %
|
||||
(visible_npus, npu_count, ','.join(npu_devices[:npu_count])))
|
||||
return npu_devices[:npu_count]
|
||||
|
||||
|
||||
def test_cases_result_to_df(result_list):
|
||||
table_header = ['Name', 'Result', 'Info', 'Start time', 'Stop time', 'Time cost(seconds)']
|
||||
df = pandas.DataFrame(result_list, columns=table_header).sort_values(by=['Start time'], ascending=True)
|
||||
return df
|
||||
|
||||
|
||||
def statistics_test_result(df):
|
||||
total_cases = df.shape[0]
|
||||
# yapf: disable
|
||||
success_cases = df.loc[df['Result'] == 'Success'].shape[0]
|
||||
error_cases = df.loc[df['Result'] == 'Error'].shape[0]
|
||||
failures_cases = df.loc[df['Result'] == 'Failures'].shape[0]
|
||||
expected_failure_cases = df.loc[df['Result'] == 'ExpectedFailures'].shape[0]
|
||||
unexpected_success_cases = df.loc[df['Result'] == 'UnexpectedSuccesses'].shape[0]
|
||||
skipped_cases = df.loc[df['Result'] == 'Skipped'].shape[0]
|
||||
# yapf: enable
|
||||
|
||||
if failures_cases > 0 or \
|
||||
error_cases > 0 or \
|
||||
unexpected_success_cases > 0:
|
||||
final_result = 'FAILED'
|
||||
else:
|
||||
final_result = 'SUCCESS'
|
||||
result_msg = '%s (Runs=%s,success=%s,failures=%s,errors=%s,\
|
||||
skipped=%s,expected failures=%s,unexpected successes=%s)' % (final_result, total_cases, success_cases,
|
||||
failures_cases, error_cases, skipped_cases,
|
||||
expected_failure_cases, unexpected_success_cases)
|
||||
|
||||
model_cases = get_case_model_info()
|
||||
for model_name, case_info in model_cases.items():
|
||||
cases = df.loc[df['Name'].str.contains('|'.join(list(case_info)))]
|
||||
results = cases['Result']
|
||||
result = None
|
||||
if any(results == 'Error') or any(results == 'Failures') or any(results == 'UnexpectedSuccesses'):
|
||||
result = ModelTag.MODEL_FAIL
|
||||
elif any(results == 'Success'):
|
||||
result = ModelTag.MODEL_PASS
|
||||
elif all(results == 'Skipped'):
|
||||
result = ModelTag.MODEL_SKIP
|
||||
else:
|
||||
print(f'invalid results for {model_name} \n{result}')
|
||||
|
||||
if result is not None:
|
||||
commit_model_ut_result(model_name, result)
|
||||
print('Testing result summary.')
|
||||
print(result_msg)
|
||||
if final_result == 'FAILED':
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def gather_test_suites_in_files(test_dir, case_file_list, list_tests):
|
||||
test_suite = unittest.TestSuite()
|
||||
for case in deduplicate_preserve_order(case_file_list):
|
||||
test_case = unittest.defaultTestLoader.discover(start_dir=test_dir, pattern=case)
|
||||
test_suite.addTest(test_case)
|
||||
if hasattr(test_case, '__iter__'):
|
||||
for subcase in test_case:
|
||||
if list_tests:
|
||||
print(subcase)
|
||||
else:
|
||||
if list_tests:
|
||||
print(test_case)
|
||||
return test_suite
|
||||
|
||||
|
||||
def gather_test_suites_files(test_dir, pattern):
|
||||
case_file_list = []
|
||||
for dirpath, dirnames, filenames in os.walk(test_dir):
|
||||
for file in filenames:
|
||||
if fnmatch(file, pattern):
|
||||
case_file_list.append(file)
|
||||
|
||||
return deduplicate_preserve_order(case_file_list)
|
||||
|
||||
|
||||
def collect_test_results(case_results):
|
||||
result_list = [] # each item is Case, Result, Start time, Stop time, Time cost
|
||||
for case_result in case_results.successes:
|
||||
result_list.append((case_result.test_full_name, 'Success', '', case_result.start_time, case_result.stop_time,
|
||||
case_result.time_cost))
|
||||
for case_result in case_results.errors:
|
||||
result_list.append((case_result[0].test_full_name, 'Error', case_result[1], case_result[0].start_time,
|
||||
case_result[0].stop_time, case_result[0].time_cost))
|
||||
for case_result in case_results.skipped:
|
||||
result_list.append((case_result[0].test_full_name, 'Skipped', case_result[1], case_result[0].start_time,
|
||||
case_result[0].stop_time, case_result[0].time_cost))
|
||||
for case_result in case_results.expectedFailures:
|
||||
result_list.append((case_result[0].test_full_name, 'ExpectedFailures', case_result[1],
|
||||
case_result[0].start_time, case_result[0].stop_time, case_result[0].time_cost))
|
||||
for case_result in case_results.failures:
|
||||
result_list.append((case_result[0].test_full_name, 'Failures', case_result[1], case_result[0].start_time,
|
||||
case_result[0].stop_time, case_result[0].time_cost))
|
||||
for case_result in case_results.unexpectedSuccesses:
|
||||
result_list.append((case_result.test_full_name, 'UnexpectedSuccesses', '', case_result.start_time,
|
||||
case_result.stop_time, case_result.time_cost))
|
||||
return result_list
|
||||
|
||||
|
||||
def run_command_with_popen(cmd):
|
||||
with subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, encoding='utf8') as sub_process:
|
||||
for line in iter(sub_process.stdout.readline, ''):
|
||||
sys.stdout.write(line)
|
||||
|
||||
|
||||
def async_run_command_with_popen(cmd, device_id):
|
||||
logger.info('Worker id: %s args: %s' % (device_id, cmd))
|
||||
env = os.environ.copy()
|
||||
visible_npus = env.get('ASCEND_RT_VISIBLE_DEVICES')
|
||||
if visible_npus:
|
||||
npu_devices = get_available_npu_devices(visible_npus)
|
||||
if npu_devices:
|
||||
env['ASCEND_RT_VISIBLE_DEVICES'] = npu_devices[device_id % len(npu_devices)]
|
||||
logger.info('Worker id: %s ASCEND_RT_VISIBLE_DEVICES: %s' % (device_id, env['ASCEND_RT_VISIBLE_DEVICES']))
|
||||
else:
|
||||
env['CUDA_VISIBLE_DEVICES'] = '%s' % device_id
|
||||
sub_process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
bufsize=1,
|
||||
universal_newlines=True,
|
||||
env=env,
|
||||
encoding='utf8')
|
||||
return sub_process
|
||||
|
||||
|
||||
def save_test_result(df, args):
|
||||
if args.result_dir is not None:
|
||||
file_name = str(int(datetime.datetime.now().timestamp() * 1000))
|
||||
os.umask(0)
|
||||
Path(args.result_dir).mkdir(mode=0o777, parents=True, exist_ok=True)
|
||||
Path(os.path.join(args.result_dir, file_name)).touch(mode=0o666, exist_ok=True)
|
||||
df.to_pickle(os.path.join(args.result_dir, file_name))
|
||||
|
||||
|
||||
def run_command(cmd):
|
||||
logger.info('Running command: %s' % ' '.join(cmd))
|
||||
response = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
try:
|
||||
response.check_returncode()
|
||||
logger.info(response.stdout.decode('utf8'))
|
||||
except subprocess.CalledProcessError as error:
|
||||
logger.error('stdout: %s, stderr: %s' % (response.stdout.decode('utf8'), error.stderr.decode('utf8')))
|
||||
|
||||
|
||||
def install_packages(pkgs):
|
||||
if pkgs is None:
|
||||
return
|
||||
cmd = [sys.executable, '-m', 'pip', 'install']
|
||||
for pkg in pkgs:
|
||||
cmd.append(pkg)
|
||||
|
||||
run_command(cmd)
|
||||
|
||||
|
||||
def install_requirements(requirements):
|
||||
for req in requirements:
|
||||
cmd = [
|
||||
sys.executable, '-m', 'pip', 'install', '-r',
|
||||
'requirements/%s' % req, '-f', 'https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html'
|
||||
]
|
||||
run_command(cmd)
|
||||
|
||||
|
||||
def wait_for_free_worker(workers):
|
||||
while True:
|
||||
for idx, worker in enumerate(workers):
|
||||
if worker is None:
|
||||
logger.info('return free worker: %s' % (idx))
|
||||
return idx
|
||||
if worker.poll() is None: # running, get output
|
||||
for line in iter(worker.stdout.readline, ''):
|
||||
if line != '':
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
break
|
||||
else: # worker process completed.
|
||||
logger.info('Process end: %s' % (idx))
|
||||
workers[idx] = None
|
||||
return idx
|
||||
time.sleep(0.001)
|
||||
|
||||
|
||||
def wait_for_workers(workers):
|
||||
while True:
|
||||
for idx, worker in enumerate(workers):
|
||||
if worker is None:
|
||||
continue
|
||||
# check worker is completed.
|
||||
if worker.poll() is None:
|
||||
for line in iter(worker.stdout.readline, ''):
|
||||
if line != '':
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
logger.info('Process idx: %s end!' % (idx))
|
||||
workers[idx] = None
|
||||
|
||||
is_all_completed = True
|
||||
for idx, worker in enumerate(workers):
|
||||
if worker is not None:
|
||||
is_all_completed = False
|
||||
break
|
||||
|
||||
if is_all_completed:
|
||||
logger.info('All sub process is completed!')
|
||||
break
|
||||
time.sleep(0.001)
|
||||
|
||||
|
||||
def parallel_run_case_in_env(env_name, env, test_suite_env_map, isolated_cases, result_dir, parallel):
|
||||
logger.info('Running case in env: %s' % env_name)
|
||||
# install requirements and deps # run_config['envs'][env]
|
||||
if 'requirements' in env:
|
||||
install_requirements(env['requirements'])
|
||||
if 'dependencies' in env:
|
||||
install_packages(env['dependencies'])
|
||||
# case worker processes
|
||||
worker_processes = [None] * parallel
|
||||
for test_suite_file in isolated_cases: # run case in subprocess
|
||||
if test_suite_file in test_suite_env_map and test_suite_env_map[test_suite_file] == env_name:
|
||||
cmd = [
|
||||
'python',
|
||||
'tests/run.py',
|
||||
'--pattern',
|
||||
test_suite_file,
|
||||
'--result_dir',
|
||||
result_dir,
|
||||
]
|
||||
worker_idx = wait_for_free_worker(worker_processes)
|
||||
worker_process = async_run_command_with_popen(cmd, worker_idx)
|
||||
os.set_blocking(worker_process.stdout.fileno(), False)
|
||||
worker_processes[worker_idx] = worker_process
|
||||
else:
|
||||
pass # case not in run list.
|
||||
|
||||
# run remain cases in a process.
|
||||
remain_suite_files = []
|
||||
for k, v in test_suite_env_map.items():
|
||||
if k not in isolated_cases and v == env_name:
|
||||
remain_suite_files.append(k)
|
||||
if len(remain_suite_files) == 0:
|
||||
wait_for_workers(worker_processes)
|
||||
return
|
||||
# roughly split case in parallel
|
||||
part_count = math.ceil(len(remain_suite_files) / parallel)
|
||||
suites_chunks = [remain_suite_files[x:x + part_count] for x in range(0, len(remain_suite_files), part_count)]
|
||||
for suites_chunk in suites_chunks:
|
||||
worker_idx = wait_for_free_worker(worker_processes)
|
||||
cmd = ['python', 'tests/run.py', '--result_dir', result_dir, '--suites']
|
||||
for suite in suites_chunk:
|
||||
cmd.append(suite)
|
||||
worker_process = async_run_command_with_popen(cmd, worker_idx)
|
||||
os.set_blocking(worker_process.stdout.fileno(), False)
|
||||
worker_processes[worker_idx] = worker_process
|
||||
|
||||
wait_for_workers(worker_processes)
|
||||
|
||||
|
||||
def run_case_in_env(env_name, env, test_suite_env_map, isolated_cases, result_dir):
|
||||
# install requirements and deps # run_config['envs'][env]
|
||||
if 'requirements' in env:
|
||||
install_requirements(env['requirements'])
|
||||
if 'dependencies' in env:
|
||||
install_packages(env['dependencies'])
|
||||
|
||||
for test_suite_file in isolated_cases: # run case in subprocess
|
||||
if test_suite_file in test_suite_env_map and test_suite_env_map[test_suite_file] == env_name:
|
||||
cmd = [
|
||||
'python',
|
||||
'tests/run.py',
|
||||
'--pattern',
|
||||
test_suite_file,
|
||||
'--result_dir',
|
||||
result_dir,
|
||||
]
|
||||
run_command_with_popen(cmd)
|
||||
else:
|
||||
pass # case not in run list.
|
||||
|
||||
# run remain cases in a process.
|
||||
remain_suite_files = []
|
||||
for k, v in test_suite_env_map.items():
|
||||
if k not in isolated_cases and v == env_name:
|
||||
remain_suite_files.append(k)
|
||||
if len(remain_suite_files) == 0:
|
||||
return
|
||||
cmd = ['python', 'tests/run.py', '--result_dir', result_dir, '--suites']
|
||||
for suite in remain_suite_files:
|
||||
cmd.append(suite)
|
||||
run_command_with_popen(cmd)
|
||||
|
||||
|
||||
def run_non_parallelizable_test_suites(suites, result_dir):
|
||||
if len(suites) == 0:
|
||||
return
|
||||
cmd = ['python', 'tests/run.py', '--result_dir', result_dir, '--suites']
|
||||
for suite in suites:
|
||||
cmd.append(suite)
|
||||
run_command_with_popen(cmd)
|
||||
|
||||
|
||||
# Selected cases:
|
||||
def get_selected_cases():
|
||||
cmd = ['python', '-u', 'tests/run_analysis.py']
|
||||
selected_cases = []
|
||||
with subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, encoding='utf8') as sub_process:
|
||||
for line in iter(sub_process.stdout.readline, ''):
|
||||
sys.stdout.write(line)
|
||||
if line.startswith('Selected cases:'):
|
||||
line = line.replace('Selected cases:', '').strip()
|
||||
selected_cases = line.split(',')
|
||||
sub_process.wait()
|
||||
if sub_process.returncode != 0:
|
||||
msg = 'Run analysis exception, returncode: %s!' % sub_process.returncode
|
||||
logger.error(msg)
|
||||
raise Exception(msg)
|
||||
return selected_cases
|
||||
|
||||
|
||||
def run_in_subprocess(args):
|
||||
# only case args.isolated_cases run in subprocess, all other run in a subprocess
|
||||
if not args.no_diff: # run based on git diff
|
||||
try:
|
||||
test_suite_files = get_selected_cases()
|
||||
logger.info('Tests suite to run: ')
|
||||
for f in test_suite_files:
|
||||
logger.info(f)
|
||||
except Exception:
|
||||
logger.error('Get test suite based diff exception!, will run all cases.')
|
||||
test_suite_files = gather_test_suites_files(os.path.abspath(args.test_dir), args.pattern)
|
||||
if len(test_suite_files) == 0:
|
||||
logger.error('Get no test suite based on diff, run all the cases.')
|
||||
test_suite_files = gather_test_suites_files(os.path.abspath(args.test_dir), args.pattern)
|
||||
else:
|
||||
test_suite_files = gather_test_suites_files(os.path.abspath(args.test_dir), args.pattern)
|
||||
test_suite_files = deduplicate_preserve_order(test_suite_files)
|
||||
|
||||
non_parallelizable_suites = []
|
||||
test_suite_files = [x for x in test_suite_files if x not in non_parallelizable_suites]
|
||||
|
||||
run_config = None
|
||||
isolated_cases = []
|
||||
test_suite_env_map = {}
|
||||
# put all the case in default env.
|
||||
for test_suite_file in test_suite_files:
|
||||
test_suite_env_map[test_suite_file] = 'default'
|
||||
|
||||
if args.run_config is not None and Path(args.run_config).exists():
|
||||
with open(args.run_config, encoding='utf-8') as f:
|
||||
run_config = yaml.safe_load(f)
|
||||
if 'isolated' in run_config:
|
||||
isolated_cases = run_config['isolated']
|
||||
|
||||
if 'envs' in run_config:
|
||||
for env in run_config['envs']:
|
||||
if env != 'default':
|
||||
for test_suite in run_config['envs'][env]['tests']:
|
||||
if test_suite in test_suite_env_map:
|
||||
test_suite_env_map[test_suite] = env
|
||||
|
||||
if args.subprocess: # run all case in subprocess
|
||||
isolated_cases = test_suite_files
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_result_dir:
|
||||
# first run cases that nonparallelizable
|
||||
run_non_parallelizable_test_suites(non_parallelizable_suites, temp_result_dir)
|
||||
|
||||
# run case parallel in envs
|
||||
for env in set(test_suite_env_map.values()):
|
||||
parallel_run_case_in_env(env, run_config['envs'][env], test_suite_env_map, isolated_cases, temp_result_dir,
|
||||
args.parallel)
|
||||
|
||||
result_dfs = []
|
||||
result_path = Path(temp_result_dir)
|
||||
for result in result_path.iterdir():
|
||||
if Path.is_file(result):
|
||||
df = pandas.read_pickle(result)
|
||||
result_dfs.append(df)
|
||||
result_pd = pandas.concat(result_dfs) # merge result of every test suite.
|
||||
print_table_result(result_pd)
|
||||
print_abnormal_case_info(result_pd)
|
||||
statistics_test_result(result_pd)
|
||||
|
||||
|
||||
def get_object_full_name(obj):
|
||||
klass = obj.__class__
|
||||
module = klass.__module__
|
||||
if module == 'builtins':
|
||||
return klass.__qualname__
|
||||
return module + '.' + klass.__qualname__
|
||||
|
||||
|
||||
class TimeCostTextTestResult(TextTestResult):
|
||||
"""Record test case time used!"""
|
||||
|
||||
def __init__(self, stream, descriptions, verbosity):
|
||||
self.successes = []
|
||||
super(TimeCostTextTestResult, self).__init__(stream, descriptions, verbosity)
|
||||
|
||||
def startTest(self, test):
|
||||
test.start_time = datetime.datetime.now()
|
||||
test.test_full_name = get_object_full_name(test) + '.' + test._testMethodName
|
||||
self.stream.writeln('Test case: %s start at: %s' % (test.test_full_name, test.start_time))
|
||||
|
||||
return super(TimeCostTextTestResult, self).startTest(test)
|
||||
|
||||
def stopTest(self, test):
|
||||
TextTestResult.stopTest(self, test)
|
||||
test.stop_time = datetime.datetime.now()
|
||||
test.time_cost = (test.stop_time - test.start_time).total_seconds()
|
||||
self.stream.writeln('Test case: %s stop at: %s, cost time: %s(seconds)' %
|
||||
(test.test_full_name, test.stop_time, test.time_cost))
|
||||
if torch.cuda.is_available() and test.time_cost > 5.0: # print nvidia-smi
|
||||
cmd = ['nvidia-smi']
|
||||
run_command_with_popen(cmd)
|
||||
super(TimeCostTextTestResult, self).stopTest(test)
|
||||
|
||||
def addSuccess(self, test):
|
||||
self.successes.append(test)
|
||||
super(TextTestResult, self).addSuccess(test)
|
||||
|
||||
|
||||
class TimeCostTextTestRunner(unittest.runner.TextTestRunner):
|
||||
resultclass = TimeCostTextTestResult
|
||||
|
||||
def run(self, test):
|
||||
return super(TimeCostTextTestRunner, self).run(test)
|
||||
|
||||
def _makeResult(self):
|
||||
result = super(TimeCostTextTestRunner, self)._makeResult()
|
||||
return result
|
||||
|
||||
|
||||
def gather_test_cases(test_dir, pattern, list_tests):
|
||||
case_list = gather_test_suites_files(test_dir, pattern)
|
||||
|
||||
test_suite = unittest.TestSuite()
|
||||
|
||||
for case in case_list:
|
||||
test_case = unittest.defaultTestLoader.discover(start_dir=test_dir, pattern=case)
|
||||
test_suite.addTest(test_case)
|
||||
if hasattr(test_case, '__iter__'):
|
||||
for subcase in test_case:
|
||||
if list_tests:
|
||||
print(subcase)
|
||||
else:
|
||||
if list_tests:
|
||||
print(test_case)
|
||||
return test_suite
|
||||
|
||||
|
||||
def print_abnormal_case_info(df):
|
||||
df = df.loc[(df['Result'] == 'Error') | (df['Result'] == 'Failures')]
|
||||
for _, row in df.iterrows():
|
||||
print('Case %s run result: %s, msg:\n%s' % (row['Name'], row['Result'], row['Info']))
|
||||
|
||||
|
||||
def print_table_result(df):
|
||||
df = df.loc[df['Result'] != 'Skipped']
|
||||
df = df.drop('Info', axis=1)
|
||||
formatters = {
|
||||
'Name': '{{:<{}s}}'.format(df['Name'].str.len().max()).format,
|
||||
'Result': '{{:<{}s}}'.format(df['Result'].str.len().max()).format,
|
||||
}
|
||||
with pandas.option_context('display.max_rows', None, 'display.max_columns', None, 'display.width', None):
|
||||
print(df.to_string(justify='left', formatters=formatters, index=False))
|
||||
|
||||
|
||||
def main(args):
|
||||
runner = TimeCostTextTestRunner()
|
||||
if args.suites is not None and len(args.suites) > 0:
|
||||
logger.info('Running: %s' % ' '.join(args.suites))
|
||||
test_suite = gather_test_suites_in_files(args.test_dir, args.suites, args.list_tests)
|
||||
else:
|
||||
test_suite = gather_test_cases(os.path.abspath(args.test_dir), args.pattern, args.list_tests)
|
||||
if not args.list_tests:
|
||||
result = runner.run(test_suite)
|
||||
logger.info('Running case completed, pid: %s, suites: %s' % (os.getpid(), args.suites))
|
||||
result = collect_test_results(result)
|
||||
df = test_cases_result_to_df(result)
|
||||
if args.result_dir is not None:
|
||||
save_test_result(df, args)
|
||||
else:
|
||||
print_table_result(df)
|
||||
print_abnormal_case_info(df)
|
||||
statistics_test_result(df)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser('test runner')
|
||||
parser.add_argument('--list_tests', action='store_true', help='list all tests')
|
||||
parser.add_argument('--pattern', default='test_*.py', help='test file pattern')
|
||||
parser.add_argument('--test_dir', default='tests', help='directory to be tested')
|
||||
parser.add_argument('--level', default=0, type=int, help='2 -- all, 1 -- p1, 0 -- p0')
|
||||
parser.add_argument('--profile', action='store_true', help='enable profiling')
|
||||
parser.add_argument('--run_config', default=None, help='specified case run config file(yaml file)')
|
||||
parser.add_argument('--subprocess', action='store_true', help='run all test suite in subprocess')
|
||||
parser.add_argument('--result_dir', default=None, help='Save result to directory, internal use only')
|
||||
parser.add_argument(
|
||||
'--parallel', default=1, type=int, help='Set case parallels, default single process, set with gpu number.')
|
||||
parser.add_argument(
|
||||
'--no-diff',
|
||||
action='store_true',
|
||||
help='Default running case based on git diff(with master), disable with --no-diff)')
|
||||
parser.add_argument('--suites', nargs='*', help='Run specified test suites(test suite files list split by space)')
|
||||
args = parser.parse_args()
|
||||
print(args)
|
||||
if args.run_config is not None or args.subprocess:
|
||||
run_in_subprocess(args)
|
||||
else:
|
||||
main(args)
|
||||
@@ -0,0 +1,8 @@
|
||||
# isolate cases in env, we can install different dependencies in each env.
|
||||
isolated: # test cases that may require excessive amount of GPU memory or run long time, which will be executed in dedicated process.
|
||||
|
||||
envs:
|
||||
default: # default env, case not in other env will in default, pytorch.
|
||||
dependencies: # requirement packages,pip install before test case run.
|
||||
# - numpy>=1.20,<=1.22.0
|
||||
# - protobuf<4,>=3.20.2
|
||||
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
|
||||
|
||||
def test_client():
|
||||
import json
|
||||
|
||||
from swift import SamplingArguments, sampling_main
|
||||
base_url = 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
||||
api_key = os.environ.get('OPENAI_API_KEY')
|
||||
engine_kwargs = json.dumps({
|
||||
'base_url': base_url,
|
||||
'api_key': api_key,
|
||||
})
|
||||
dataset = 'tastelikefeet/competition_math#5'
|
||||
system = """A conversation between User and Assistant. The user asks a question, and the Assistant solves it.
|
||||
The assistant first thinks about the reasoning process in the mind and then provides the user
|
||||
with the answer. The reasoning process and answer are enclosed
|
||||
within <think> </think> and <answer> </answer> tags, respectively,
|
||||
i.e., <think> reasoning process here </think> <answer> answer here </answer>."""
|
||||
args = SamplingArguments(
|
||||
sampler_type='distill',
|
||||
sampler_engine='client',
|
||||
model='deepseek-r1',
|
||||
dataset=dataset,
|
||||
num_return_sequences=1,
|
||||
stream=True,
|
||||
system=system,
|
||||
temperature=0.6,
|
||||
top_p=0.95,
|
||||
engine_kwargs=engine_kwargs,
|
||||
)
|
||||
sampling_main(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_client()
|
||||
@@ -0,0 +1,59 @@
|
||||
import os
|
||||
from pprint import pprint
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 4,
|
||||
'per_device_eval_batch_size': 4,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
'save_steps': 100,
|
||||
'max_length': 512,
|
||||
'task_type': 'seq_cls',
|
||||
'num_labels': 2,
|
||||
}
|
||||
|
||||
|
||||
def calc_acc(infer_result):
|
||||
n_correct = 0
|
||||
for res in infer_result:
|
||||
if res['response'] == res['labels']:
|
||||
n_correct += 1
|
||||
return f'acc: {n_correct / len(infer_result)}, n_correct: {n_correct}, len(res): {len(infer_result)}'
|
||||
|
||||
|
||||
def test_llm():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
res = []
|
||||
for model in ['Qwen/Qwen2.5-0.5B-Instruct', 'Qwen/Qwen2.5-0.5B', 'AI-ModelScope/bert-base-chinese']:
|
||||
dataset = ['DAMO_NLP/jd:cls#2000']
|
||||
result = sft_main(SftArguments(model=model, dataset=dataset, split_dataset_ratio=0.1, **kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_result = infer_main(
|
||||
InferArguments(adapters=[last_model_checkpoint], load_data_args=True, truncation_strategy='right'))
|
||||
res.append(calc_acc(infer_result))
|
||||
infer_result2 = infer_main(
|
||||
InferArguments(
|
||||
adapters=[last_model_checkpoint], load_data_args=True, max_batch_size=16, truncation_strategy='right'))
|
||||
res.append(calc_acc(infer_result2))
|
||||
|
||||
model = 'Qwen/Qwen2.5-0.5B-Instruct'
|
||||
dataset = ['DAMO_NLP/jd#2000']
|
||||
train_kwargs = kwargs.copy()
|
||||
train_kwargs.pop('task_type')
|
||||
train_kwargs.pop('num_labels')
|
||||
result = sft_main(SftArguments(model=model, dataset=dataset, split_dataset_ratio=0.1, **train_kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_result = infer_main(
|
||||
InferArguments(adapters=[last_model_checkpoint], load_data_args=True, truncation_strategy='right'))
|
||||
res.append(calc_acc(infer_result))
|
||||
infer_result2 = infer_main(
|
||||
InferArguments(
|
||||
adapters=[last_model_checkpoint], load_data_args=True, max_batch_size=16, truncation_strategy='right'))
|
||||
res.append(calc_acc(infer_result2))
|
||||
pprint(res)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_llm()
|
||||
@@ -0,0 +1,83 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def _infer_image(model, system=None, images=None):
|
||||
engine = LmdeployEngine(model)
|
||||
if images is None:
|
||||
images = ['http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png']
|
||||
messages = []
|
||||
if system is not None:
|
||||
messages += [{'role': 'system', 'content': system}]
|
||||
messages.append({'role': 'user', 'content': 'describe the image.'})
|
||||
resp_list = engine.infer([InferRequest(messages=messages, images=images)],
|
||||
RequestConfig(temperature=0, max_tokens=64, repetition_penalty=1.))
|
||||
return resp_list[0].choices[0].message.content
|
||||
|
||||
|
||||
def _infer_image_pipeline(model, images=None, prefix='<IMAGE_TOKEN>\n'):
|
||||
from lmdeploy import GenerationConfig, pipeline
|
||||
from lmdeploy.vl import load_image
|
||||
|
||||
from swift.utils import safe_snapshot_download
|
||||
gen_config = GenerationConfig(temperature=0., repetition_penalty=1., max_new_tokens=64)
|
||||
pipe = pipeline(safe_snapshot_download(model))
|
||||
|
||||
image = load_image('http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png')
|
||||
response = pipe((f'{prefix}describe the image.', image), gen_config=gen_config)
|
||||
return response.text
|
||||
|
||||
|
||||
def test_internvl2_5():
|
||||
model = 'OpenGVLab/InternVL2_5-4B'
|
||||
response = _infer_image(model)
|
||||
response2 = _infer_image_pipeline(model)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_internvl2():
|
||||
model = 'OpenGVLab/InternVL2-2B'
|
||||
response = _infer_image(model)
|
||||
response2 = _infer_image_pipeline(model) # Missing '\n' after '<|im_end|>'
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_deepseek_vl():
|
||||
model = 'deepseek-ai/deepseek-vl-1.3b-chat'
|
||||
response = _infer_image(model)
|
||||
response2 = _infer_image_pipeline(model, prefix='<IMAGE_TOKEN>')
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_qwen_vl():
|
||||
model = 'Qwen/Qwen-VL-Chat'
|
||||
response = _infer_image_pipeline(model) # Missing: 'Picture 1: '
|
||||
response2 = _infer_image(model)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_qwen2_vl():
|
||||
model = 'Qwen/Qwen2-VL-2B-Instruct'
|
||||
response = _infer_image_pipeline(model, prefix='<IMAGE_TOKEN>')
|
||||
response2 = _infer_image(model)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_qwen2_5_vl():
|
||||
model = 'Qwen/Qwen2.5-VL-3B-Instruct'
|
||||
response = _infer_image(model)
|
||||
response2 = _infer_image_pipeline(model, prefix='<IMAGE_TOKEN>')
|
||||
assert response == response2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from swift.infer_engine import InferRequest, LmdeployEngine, RequestConfig
|
||||
|
||||
# test_internvl2()
|
||||
# test_internvl2_5()
|
||||
# test_deepseek_vl()
|
||||
# test_qwen_vl()
|
||||
# test_qwen2_vl()
|
||||
test_qwen2_5_vl()
|
||||
@@ -0,0 +1,568 @@
|
||||
import copy
|
||||
import os
|
||||
import pytest
|
||||
import torch
|
||||
from contextlib import contextmanager, nullcontext
|
||||
from typing import Any, Dict
|
||||
|
||||
from swift.model import get_processor
|
||||
from swift.template import get_template
|
||||
|
||||
try:
|
||||
from vllm.config import ModelConfig
|
||||
from vllm.multimodal import MULTIMODAL_REGISTRY
|
||||
from vllm.multimodal.inputs import nested_tensors_equal
|
||||
except ImportError:
|
||||
ModelConfig = None
|
||||
MULTIMODAL_REGISTRY = None
|
||||
nested_tensors_equal = None
|
||||
|
||||
pytestmark = pytest.mark.skipif(ModelConfig is None, reason='vLLM not available')
|
||||
|
||||
WEATHER_AUDIO = 'http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/weather.wav'
|
||||
BABY_VIDEO = 'https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4'
|
||||
DRAW_VIDEO = 'https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2.5-Omni/draw.mp4'
|
||||
CAT_IMAGE = 'http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png'
|
||||
_SKIP_TRAIN_KEYS = frozenset({'input_ids', 'labels', 'loss_scale', 'mm_token_type_ids'})
|
||||
|
||||
_QWEN_VL_VIDEO_ALIASES = {'video_second_per_grid': 'second_per_grid_ts'}
|
||||
_GEMMA4_IMAGE_ALIASES = {'image_position_ids': 'pixel_position_ids'}
|
||||
_QWEN3_OMNI_AUDIO_ALIASES = {
|
||||
'input_features': 'input_audio_features',
|
||||
'feature_attention_mask': 'feature_attention_mask',
|
||||
}
|
||||
_XFAIL_TESTS = frozenset({
|
||||
'test_qwen2_5_vl_video',
|
||||
'test_qwen3_vl_video',
|
||||
'test_qwen3_5_video',
|
||||
'test_gemma4_video',
|
||||
})
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def as_list_ids(x):
|
||||
if isinstance(x, torch.Tensor):
|
||||
return x.reshape(-1).tolist()
|
||||
return list(x)
|
||||
|
||||
|
||||
def tensors_aligned(a, b) -> bool:
|
||||
if isinstance(a, list) and isinstance(b, list):
|
||||
return len(a) == len(b) and all(tensors_aligned(x, y) for x, y in zip(a, b))
|
||||
if isinstance(a, list) and len(a) == 1:
|
||||
a = a[0]
|
||||
if isinstance(b, list) and len(b) == 1:
|
||||
b = b[0]
|
||||
if isinstance(a, torch.Tensor) and isinstance(b, torch.Tensor):
|
||||
a, b = a.detach().cpu(), b.detach().cpu()
|
||||
if a.ndim == b.ndim + 1 and a.shape[0] == 1 and a.shape[1:] == b.shape:
|
||||
a = a.squeeze(0)
|
||||
elif b.ndim == a.ndim + 1 and b.shape[0] == 1 and b.shape[1:] == a.shape:
|
||||
b = b.squeeze(0)
|
||||
if a.shape != b.shape:
|
||||
return False
|
||||
if a.dtype.is_floating_point or b.dtype.is_floating_point:
|
||||
a = a.to(torch.bfloat16).float()
|
||||
b = b.to(torch.bfloat16).float()
|
||||
return torch.allclose(a, b, rtol=0, atol=0)
|
||||
return torch.equal(a, b)
|
||||
if nested_tensors_equal is None:
|
||||
return a == b
|
||||
return nested_tensors_equal(a, b)
|
||||
|
||||
|
||||
def build_vllm_mm_data(vllm_encoded: Dict[str, Any]) -> Dict[str, Any]:
|
||||
mm_data = {}
|
||||
for plural, singular in [('images', 'image'), ('videos', 'video'), ('audios', 'audio')]:
|
||||
data = vllm_encoded.get(plural)
|
||||
if not data:
|
||||
continue
|
||||
if len(data) == 1 and not isinstance(data[0], tuple):
|
||||
mm_data[singular] = data[0]
|
||||
else:
|
||||
mm_data[singular] = data
|
||||
return mm_data
|
||||
|
||||
|
||||
def swift_train_encode(template, sample: dict) -> Dict[str, Any]:
|
||||
train_template = copy.deepcopy(template)
|
||||
train_template.set_mode('train')
|
||||
return train_template.encode(sample)
|
||||
|
||||
|
||||
def vllm_forward_kwargs(model_id: str, template, sample: dict) -> Dict[str, Any]:
|
||||
if ModelConfig is None:
|
||||
raise RuntimeError('vLLM is not available')
|
||||
vllm_template = copy.deepcopy(template)
|
||||
vllm_template.set_mode('vllm')
|
||||
encoded = vllm_template.encode(sample)
|
||||
mm_data = build_vllm_mm_data(encoded)
|
||||
if not mm_data:
|
||||
return {'input_ids': encoded['input_ids'], 'mm_tensors': {}}
|
||||
model_config = ModelConfig(model_id, trust_remote_code=True, dtype='auto', seed=0)
|
||||
processor = MULTIMODAL_REGISTRY.create_processor(model_config)
|
||||
mm_items = processor.info.parse_mm_data(mm_data)
|
||||
result = processor(
|
||||
encoded['input_ids'],
|
||||
mm_items=mm_items,
|
||||
hf_processor_mm_kwargs=encoded.get('mm_processor_kwargs') or {},
|
||||
)
|
||||
return {
|
||||
'input_ids': result['prompt_token_ids'],
|
||||
'mm_tensors': result['mm_kwargs'].get_data(),
|
||||
}
|
||||
|
||||
|
||||
@contextmanager
|
||||
def audio_backend(backend: str):
|
||||
prev = os.environ.get('SWIFT_AUDIO_LOAD_BACKEND')
|
||||
os.environ['SWIFT_AUDIO_LOAD_BACKEND'] = backend
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if prev is None:
|
||||
os.environ.pop('SWIFT_AUDIO_LOAD_BACKEND', None)
|
||||
else:
|
||||
os.environ['SWIFT_AUDIO_LOAD_BACKEND'] = prev
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _soundfile_pyav_for_align_tests():
|
||||
with audio_backend('soundfile_pyav'):
|
||||
yield
|
||||
|
||||
|
||||
def _vllm_audio_feature_lengths(train: dict, vllm_tensors: dict) -> None:
|
||||
"""vLLM sets audio_feature_lengths; Swift derives the same value from mask.sum()."""
|
||||
vllm_afl = vllm_tensors.get('audio_feature_lengths')
|
||||
mask = train.get('feature_attention_mask')
|
||||
if vllm_afl is None or mask is None:
|
||||
return
|
||||
derived = mask.sum(-1)
|
||||
if derived.ndim == 0:
|
||||
derived = derived.unsqueeze(0)
|
||||
assert tensors_aligned(derived, vllm_afl), 'mask.sum() != vLLM audio_feature_lengths'
|
||||
|
||||
|
||||
@contextmanager
|
||||
def use_audio_in_video(enabled: bool = True):
|
||||
prev = os.environ.get('USE_AUDIO_IN_VIDEO')
|
||||
if enabled:
|
||||
os.environ['USE_AUDIO_IN_VIDEO'] = 'true'
|
||||
else:
|
||||
os.environ.pop('USE_AUDIO_IN_VIDEO', None)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if prev is None:
|
||||
os.environ.pop('USE_AUDIO_IN_VIDEO', None)
|
||||
else:
|
||||
os.environ['USE_AUDIO_IN_VIDEO'] = prev
|
||||
|
||||
|
||||
def _assert_mm_align(
|
||||
model_id,
|
||||
sample,
|
||||
*,
|
||||
tensor_key_aliases=None,
|
||||
check_input_ids=True,
|
||||
check_vllm_audio_feature_lengths=False,
|
||||
use_audio_in_video_flag=False,
|
||||
):
|
||||
tensor_key_aliases = tensor_key_aliases or {}
|
||||
ctx = use_audio_in_video() if use_audio_in_video_flag else nullcontext()
|
||||
with ctx:
|
||||
processor = get_processor(model_id)
|
||||
template = get_template(processor)
|
||||
train = swift_train_encode(template, sample)
|
||||
vllm = vllm_forward_kwargs(model_id, template, sample)
|
||||
|
||||
if check_input_ids:
|
||||
assert as_list_ids(train['input_ids']) == as_list_ids(vllm['input_ids'])
|
||||
|
||||
vllm_tensors = dict(vllm['mm_tensors'])
|
||||
compared = [(tk, tensor_key_aliases.get(tk, tk))
|
||||
for tk in sorted(k for k, v in train.items() if v is not None and k not in _SKIP_TRAIN_KEYS)
|
||||
if tensor_key_aliases.get(tk, tk) in vllm_tensors]
|
||||
for train_key, vllm_key in compared:
|
||||
assert tensors_aligned(train[train_key], vllm_tensors[vllm_key]), f'{train_key}!={vllm_key}'
|
||||
if check_vllm_audio_feature_lengths:
|
||||
_vllm_audio_feature_lengths(train, vllm_tensors)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Qwen2.5-VL
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_qwen2_5_vl_image():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen2.5-VL-3B-Instruct',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the image.'
|
||||
}],
|
||||
'images': [CAT_IMAGE]
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='vLLM Qwen2_5_VLProcessor rejects fps list in mm_processor_kwargs (expects scalar)')
|
||||
def test_qwen2_5_vl_video():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen2.5-VL-3B-Instruct',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the video.'
|
||||
}],
|
||||
'videos': [BABY_VIDEO]
|
||||
},
|
||||
tensor_key_aliases=_QWEN_VL_VIDEO_ALIASES,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Qwen3-VL
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_qwen3_vl_image():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen3-VL-2B-Instruct',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the image.'
|
||||
}],
|
||||
'images': [CAT_IMAGE]
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='vLLM get_video_repl drops outer vision_start/end wrapper (2-token diff vs HF)')
|
||||
def test_qwen3_vl_video():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen3-VL-2B-Instruct',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the video.'
|
||||
}],
|
||||
'videos': [BABY_VIDEO]
|
||||
},
|
||||
tensor_key_aliases=_QWEN_VL_VIDEO_ALIASES,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Qwen3.5
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_qwen3_5_image():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen3.5-2B',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the image.'
|
||||
}],
|
||||
'images': [CAT_IMAGE]
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='vLLM get_video_repl drops outer vision_start/end wrapper (2-token diff vs HF)')
|
||||
def test_qwen3_5_video():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen3.5-2B',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the video.'
|
||||
}],
|
||||
'videos': [BABY_VIDEO]
|
||||
},
|
||||
tensor_key_aliases=_QWEN_VL_VIDEO_ALIASES,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Qwen2.5-Omni
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_qwen2_5_omni_image():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen2.5-Omni-7B',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the image.'
|
||||
}],
|
||||
'images': [CAT_IMAGE]
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_qwen2_5_omni_video():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen2.5-Omni-7B',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the video.'
|
||||
}],
|
||||
'videos': [BABY_VIDEO]
|
||||
},
|
||||
tensor_key_aliases={'video_second_per_grid': 'second_per_grid_ts'},
|
||||
)
|
||||
|
||||
|
||||
def test_qwen2_5_omni_audio():
|
||||
# Standalone audio: sample has `audios` field (not extracted from video).
|
||||
# vLLM path loads as (wav, sr) in _preprocess_inputs; train path uses ndarray.
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen2.5-Omni-7B',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the audio.'
|
||||
}],
|
||||
'audios': [WEATHER_AUDIO]
|
||||
},
|
||||
check_vllm_audio_feature_lengths=True,
|
||||
)
|
||||
|
||||
|
||||
def test_qwen2_5_omni_video_use_audio_in_video():
|
||||
# Video track extracted in replace_tag; vLLM uses different audio/video token layout.
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen2.5-Omni-7B',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the video.'
|
||||
}],
|
||||
'videos': [DRAW_VIDEO]
|
||||
},
|
||||
tensor_key_aliases={'video_second_per_grid': 'second_per_grid_ts'},
|
||||
check_input_ids=False,
|
||||
check_vllm_audio_feature_lengths=True,
|
||||
use_audio_in_video_flag=True,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Qwen3-Omni
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_qwen3_omni_image():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen3-Omni-30B-A3B-Instruct',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the image.'
|
||||
}],
|
||||
'images': [CAT_IMAGE]
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_qwen3_omni_video():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen3-Omni-30B-A3B-Instruct',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the video.'
|
||||
}],
|
||||
'videos': [BABY_VIDEO]
|
||||
},
|
||||
tensor_key_aliases=_QWEN_VL_VIDEO_ALIASES,
|
||||
)
|
||||
|
||||
|
||||
def test_qwen3_omni_audio():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen3-Omni-30B-A3B-Instruct',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the audio.'
|
||||
}],
|
||||
'audios': [WEATHER_AUDIO]
|
||||
},
|
||||
tensor_key_aliases=_QWEN3_OMNI_AUDIO_ALIASES,
|
||||
check_vllm_audio_feature_lengths=True,
|
||||
)
|
||||
|
||||
|
||||
def test_qwen3_omni_audio_non_hop_aligned(tmp_path):
|
||||
"""Verify hop-length floor trim when waveform length is not hop-aligned."""
|
||||
import soundfile as sf
|
||||
|
||||
from swift.template.vision_utils import load_audio
|
||||
|
||||
hop = 160
|
||||
wav = load_audio(WEATHER_AUDIO, 16000)
|
||||
n = len(wav)
|
||||
rem = n % hop
|
||||
cut = rem if rem else hop // 2 + 1
|
||||
wav = wav[:max(n - cut, hop + 1)]
|
||||
assert len(wav) % hop != 0, 'test fixture must be non hop-aligned'
|
||||
wav_path = tmp_path / 'non_hop_aligned.wav'
|
||||
sf.write(str(wav_path), wav, 16000)
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen3-Omni-30B-A3B-Instruct',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the audio.'
|
||||
}],
|
||||
'audios': [str(wav_path)]
|
||||
},
|
||||
tensor_key_aliases=_QWEN3_OMNI_AUDIO_ALIASES,
|
||||
check_vllm_audio_feature_lengths=True,
|
||||
)
|
||||
|
||||
|
||||
def test_qwen3_omni_video_use_audio_in_video():
|
||||
_assert_mm_align(
|
||||
'Qwen/Qwen3-Omni-30B-A3B-Instruct',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the video.'
|
||||
}],
|
||||
'videos': [DRAW_VIDEO]
|
||||
},
|
||||
tensor_key_aliases={
|
||||
**_QWEN3_OMNI_AUDIO_ALIASES,
|
||||
**_QWEN_VL_VIDEO_ALIASES
|
||||
},
|
||||
check_input_ids=False,
|
||||
check_vllm_audio_feature_lengths=True,
|
||||
use_audio_in_video_flag=True,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Gemma4
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_gemma4_image():
|
||||
_assert_mm_align(
|
||||
'google/gemma-4-E2B-it',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the image.'
|
||||
}],
|
||||
'images': [CAT_IMAGE]
|
||||
},
|
||||
tensor_key_aliases=_GEMMA4_IMAGE_ALIASES,
|
||||
)
|
||||
|
||||
|
||||
def test_gemma4_audio():
|
||||
_assert_mm_align(
|
||||
'google/gemma-4-E2B-it',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the audio.'
|
||||
}],
|
||||
'audios': [WEATHER_AUDIO]
|
||||
},
|
||||
tensor_key_aliases={
|
||||
'input_features': 'input_features_padded',
|
||||
'input_features_mask': 'input_features_mask',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_gemma4_audio_collator_3d():
|
||||
"""Collator must batch audio as (N, max_len, feat_dim), not concat along time dim."""
|
||||
sample = {
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': 'describe the audio.'
|
||||
}],
|
||||
'audios': [WEATHER_AUDIO],
|
||||
}
|
||||
processor = get_processor('google/gemma-4-E2B-it')
|
||||
template = get_template(processor)
|
||||
template.set_mode('train')
|
||||
batch = [template.encode(sample), template.encode(sample)]
|
||||
collated = template._data_collator_mm_data(batch)
|
||||
assert collated['input_features'].ndim == 3
|
||||
assert collated['input_features'].shape[0] == 2
|
||||
assert collated['input_features_mask'].ndim == 2
|
||||
assert collated['input_features_mask'].shape[0] == 2
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='vLLM gemma4 video timestamp/soft-token path differs from HF Gemma4VideoProcessor')
|
||||
def test_gemma4_video():
|
||||
_assert_mm_align(
|
||||
'google/gemma-4-E2B-it',
|
||||
{
|
||||
'messages': [{
|
||||
'role': 'user',
|
||||
'content': '<video>describe the video.'
|
||||
}],
|
||||
'videos': [BABY_VIDEO]
|
||||
},
|
||||
tensor_key_aliases={
|
||||
'pixel_values_videos': 'pixel_values_videos',
|
||||
'video_position_ids': 'video_position_ids',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
tests = [
|
||||
test_qwen2_5_vl_image,
|
||||
test_qwen2_5_vl_video,
|
||||
test_qwen3_vl_image,
|
||||
test_qwen3_vl_video,
|
||||
test_qwen3_5_image,
|
||||
test_qwen3_5_video,
|
||||
test_qwen2_5_omni_image,
|
||||
test_qwen2_5_omni_video,
|
||||
test_qwen2_5_omni_audio,
|
||||
test_qwen2_5_omni_video_use_audio_in_video,
|
||||
test_qwen3_omni_image,
|
||||
test_qwen3_omni_video,
|
||||
test_qwen3_omni_audio,
|
||||
test_qwen3_omni_audio_non_hop_aligned,
|
||||
test_qwen3_omni_video_use_audio_in_video,
|
||||
test_gemma4_image,
|
||||
test_gemma4_audio,
|
||||
test_gemma4_audio_collator_3d,
|
||||
test_gemma4_video,
|
||||
]
|
||||
passed = xfailed = failed = 0
|
||||
for fn in tests:
|
||||
name = fn.__name__
|
||||
try:
|
||||
fn()
|
||||
print(f'{name}: PASS')
|
||||
passed += 1
|
||||
except Exception:
|
||||
if name in _XFAIL_TESTS:
|
||||
print(f'{name}: XFAIL (expected upstream vLLM mismatch)')
|
||||
xfailed += 1
|
||||
else:
|
||||
print(f'{name}: FAIL')
|
||||
failed += 1
|
||||
raise
|
||||
print(f'all mm processor align tests finished: {passed} passed, {xfailed} xfailed, {failed} failed')
|
||||
@@ -0,0 +1,73 @@
|
||||
import os
|
||||
from pprint import pprint
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 4,
|
||||
'per_device_eval_batch_size': 4,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
'save_steps': 100,
|
||||
'max_length': 8192,
|
||||
}
|
||||
|
||||
|
||||
def calc_acc(infer_result):
|
||||
n_correct = 0
|
||||
for res in infer_result:
|
||||
if res['response'] == res['labels']:
|
||||
n_correct += 1
|
||||
return f'acc: {n_correct / len(infer_result)}, n_correct: {n_correct}, len(res): {len(infer_result)}'
|
||||
|
||||
|
||||
def calc_diff(infer_result, infer_result2):
|
||||
n_correct = 0
|
||||
for x1, x2 in zip(infer_result, infer_result2):
|
||||
if x1['response'] == x2['response']:
|
||||
n_correct += 1
|
||||
return f'acc: {n_correct / len(infer_result)}, n_correct: {n_correct}, len(res): {len(infer_result)}'
|
||||
|
||||
|
||||
def test_llm():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
res = []
|
||||
for padding_side in ['left', 'right']:
|
||||
model = 'Qwen/Qwen2.5-0.5B-Instruct'
|
||||
dataset = ['damo/zh_cls_fudan-news#2000']
|
||||
result = sft_main(
|
||||
SftArguments(model=model, dataset=dataset, split_dataset_ratio=0.1, padding_side=padding_side, **kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_result = infer_main(InferArguments(adapters=[last_model_checkpoint], load_data_args=True))
|
||||
res.append(calc_acc(infer_result))
|
||||
infer_result2 = infer_main(
|
||||
InferArguments(adapters=[last_model_checkpoint], load_data_args=True, max_batch_size=16))
|
||||
res.append(calc_acc(infer_result2))
|
||||
pprint(res)
|
||||
|
||||
|
||||
def test_mllm():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
from swift.template import Template
|
||||
res = []
|
||||
for padding_side in ['left', 'right']:
|
||||
model = 'Qwen/Qwen2-VL-2B-Instruct'
|
||||
dataset = ['AI-ModelScope/LaTeX_OCR#2000']
|
||||
result = sft_main(
|
||||
SftArguments(model=model, dataset=dataset, split_dataset_ratio=0.01, padding_side=padding_side, **kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_result = infer_main(InferArguments(adapters=[last_model_checkpoint], load_data_args=True))
|
||||
res.append(infer_result)
|
||||
infer_result2 = infer_main(
|
||||
InferArguments(adapters=[last_model_checkpoint], load_data_args=True, max_batch_size=16))
|
||||
res.append(infer_result2)
|
||||
print(calc_diff(res[0], res[1]))
|
||||
print(calc_diff(res[2], res[3]))
|
||||
print(calc_diff(res[0], res[2]))
|
||||
print(calc_diff(res[0], res[3]))
|
||||
print(calc_diff(res[2], res[1]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_llm()
|
||||
test_mllm()
|
||||
@@ -0,0 +1,748 @@
|
||||
import os
|
||||
|
||||
os.environ['SWIFT_DEBUG'] = '1'
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
system = 'You are a helpful assistant.'
|
||||
|
||||
tools = [{
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'get_current_weather',
|
||||
'description': 'Get the current weather in a given location',
|
||||
'parameters': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'location': {
|
||||
'type': 'string',
|
||||
'description': 'The city and state, e.g. San Francisco, CA'
|
||||
},
|
||||
'unit': {
|
||||
'type': 'string',
|
||||
'enum': ['celsius', 'fahrenheit']
|
||||
}
|
||||
},
|
||||
'required': ['location']
|
||||
}
|
||||
}
|
||||
}, {
|
||||
'name_for_model': 'tool2',
|
||||
'name_for_human': '工具2',
|
||||
'description': 'Tool2的描述',
|
||||
}]
|
||||
|
||||
glm4_tools = [{
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'realtime_aqi',
|
||||
'description': '天气预报。获取实时空气质量。当前空气质量,PM2.5,PM10信息',
|
||||
'parameters': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'city': {
|
||||
'description': '城市名'
|
||||
}
|
||||
},
|
||||
'required': ['city']
|
||||
}
|
||||
}
|
||||
}]
|
||||
glm4_tool_messasges = [
|
||||
{
|
||||
'role': 'tool',
|
||||
'content': '{"city": "北京", "aqi": "10", "unit": "celsius"}'
|
||||
},
|
||||
{
|
||||
'role': 'tool',
|
||||
'content': '{"city": "上海", "aqi": "72", "unit": "fahrenheit"}'
|
||||
},
|
||||
]
|
||||
glm4_query = '北京和上海今天的天气情况'
|
||||
|
||||
|
||||
def _infer(engine, num_tools: int = 1, agent_tools=None, tool_messages=None, query=None):
|
||||
if agent_tools is None:
|
||||
agent_tools = tools
|
||||
if tool_messages is None:
|
||||
tool_messages = []
|
||||
for _ in range(num_tools):
|
||||
tool_messages.append({
|
||||
'role': 'tool',
|
||||
'content': '{"temperature": 32, "condition": "Sunny", "humidity": 50}'
|
||||
})
|
||||
stop = [engine.template.agent_template.keyword.observation]
|
||||
query = query or "How's the weather in Beijing today?"
|
||||
infer_request = InferRequest([{'role': 'user', 'content': query}], tools=agent_tools)
|
||||
request_config = RequestConfig(max_tokens=512, stop=stop, temperature=0)
|
||||
resp_list = engine.infer([infer_request], request_config=request_config)
|
||||
response = resp_list[0].choices[0].message.content
|
||||
toolcall = resp_list[0].choices[0].message.tool_calls[0].function
|
||||
print(f'response: {response}')
|
||||
print(f'toolcall: {toolcall}')
|
||||
assert toolcall is not None
|
||||
infer_request.messages.append({'role': 'assistant', 'content': response})
|
||||
infer_request.messages += tool_messages
|
||||
resp_list = engine.infer([infer_request], request_config=request_config)
|
||||
response2 = resp_list[0].choices[0].message.content
|
||||
print(f'response2: {response2}')
|
||||
infer_request.messages.append({'role': 'assistant', 'content': response2})
|
||||
return infer_request.messages
|
||||
|
||||
|
||||
def test_react_en():
|
||||
agent_template = agent_template_map['react_en']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 1144
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct')
|
||||
template = engine.template
|
||||
template._agent_template = 'react_en'
|
||||
messages = _infer(engine)
|
||||
assert messages[-1]['content'] == (
|
||||
'Thought: The current temperature in Beijing is 32 degrees Celsius, and the condition is sunny '
|
||||
'with a humidity of 50%.\nFinal Answer: The current temperature in Beijing is 32 degrees Celsius,'
|
||||
' and the condition is sunny with a humidity of 50%.')
|
||||
template.set_mode('train')
|
||||
encoded = template.encode({'messages': messages})
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
|
||||
def test_react_zh():
|
||||
agent_template = agent_template_map['react_zh']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 712
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct')
|
||||
template = engine.template
|
||||
template._agent_template = 'react_zh'
|
||||
_infer(engine)
|
||||
|
||||
|
||||
def test_qwen_en():
|
||||
agent_template = agent_template_map['qwen_en']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 879
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct')
|
||||
template = engine.template
|
||||
template._agent_template = 'qwen_en'
|
||||
messages = _infer(engine)
|
||||
assert messages[-1]['content'] == (
|
||||
'✿RETURN✿: Today in Beijing, the temperature is 32°C with sunny conditions and the humidity '
|
||||
'is at 50%. Enjoy the nice weather!')
|
||||
template.set_mode('train')
|
||||
encoded = template.encode({'messages': messages})
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
|
||||
def test_qwen_zh():
|
||||
agent_template = agent_template_map['qwen_zh']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 577
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct')
|
||||
template = engine.template
|
||||
template._agent_template = 'qwen_zh'
|
||||
_infer(engine)
|
||||
|
||||
|
||||
def test_qwen_en_parallel():
|
||||
agent_template = agent_template_map['qwen_en_parallel']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 1012
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct')
|
||||
template = engine.template
|
||||
template._agent_template = 'qwen_en_parallel'
|
||||
messages = _infer(engine, num_tools=2)
|
||||
assert messages[-1]['content'] == (
|
||||
'✿RETURN✿: Today in Beijing, the temperature is 32 degrees Celsius with sunny conditions '
|
||||
'and the humidity is at 50%. Enjoy the nice weather!')
|
||||
template.set_mode('train')
|
||||
encoded = template.encode({'messages': messages})
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
|
||||
def test_qwen_zh_parallel():
|
||||
agent_template = agent_template_map['qwen_zh_parallel']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 688
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct')
|
||||
template = engine.template
|
||||
template._agent_template = 'qwen_zh_parallel'
|
||||
_infer(engine, num_tools=2)
|
||||
|
||||
|
||||
def test_hermes():
|
||||
agent_template = agent_template_map['hermes']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 875
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct')
|
||||
template = engine.template
|
||||
template._agent_template = 'hermes'
|
||||
messages = _infer(engine, num_tools=2)
|
||||
template.template_backend = 'jinja'
|
||||
messages2 = _infer(engine, num_tools=2)
|
||||
assert messages[-1]['content'] == messages2[-1]['content'] == (
|
||||
'Today in Beijing, the temperature is 32 degrees Celsius with sunny conditions '
|
||||
'and the humidity is at 50%. Enjoy the nice weather!')
|
||||
template.set_mode('train')
|
||||
encoded = template.encode({'messages': messages})
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
template.template_backend = 'jinja'
|
||||
encoded2 = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded2["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded2["labels"])}')
|
||||
assert encoded['input_ids'] == encoded2['input_ids']
|
||||
|
||||
|
||||
def test_toolbench():
|
||||
agent_template = agent_template_map['toolbench']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 1833
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct')
|
||||
template = engine.template
|
||||
template._agent_template = 'toolbench'
|
||||
_infer(engine)
|
||||
|
||||
|
||||
def test_chatglm4():
|
||||
agent_template = agent_template_map['chatglm4']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 846
|
||||
engine = TransformersEngine('ZhipuAI/glm-4-9b-chat')
|
||||
template = engine.template
|
||||
template._agent_template = 'chatglm4'
|
||||
_infer(engine, agent_tools=glm4_tools, tool_messages=glm4_tool_messasges, query=glm4_query)
|
||||
|
||||
|
||||
def test_glm4():
|
||||
agent_template = agent_template_map['glm4']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 769
|
||||
engine = TransformersEngine('ZhipuAI/GLM-4-9B-0414')
|
||||
template = engine.template
|
||||
template._agent_template = 'glm4'
|
||||
messages = _infer(engine, agent_tools=glm4_tools, tool_messages=glm4_tool_messasges, query=glm4_query)
|
||||
assert messages[-1]['content'] == '根据天气预报工具,北京今天的空气质量指数为10,属于良好水平;上海今天的空气质量指数为72,属于轻度污染水平。'
|
||||
template.set_mode('train')
|
||||
encoded = template.encode({'messages': messages})
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
|
||||
def test_llama3():
|
||||
engine = TransformersEngine('LLM-Research/Llama-3.2-3B-Instruct')
|
||||
template = engine.template
|
||||
template._agent_template = 'llama3'
|
||||
messages = _infer(engine)
|
||||
|
||||
template.set_mode('train')
|
||||
encoded = template.encode({'messages': messages})
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
|
||||
def test_llama4():
|
||||
engine = TransformersEngine('LLM-Research/Llama-4-Scout-17B-16E-Instruct')
|
||||
template = engine.template
|
||||
messages = _infer(engine)
|
||||
template.set_mode('train')
|
||||
encoded = template.encode({'messages': messages})
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
|
||||
def test_hunyuan():
|
||||
engine = TransformersEngine('Tencent-Hunyuan/Hunyuan-1.8B-Instruct')
|
||||
template = engine.template
|
||||
template.template_backend = 'jinja'
|
||||
_infer(engine, num_tools=2)
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
template.set_mode('train')
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
template.template_backend = 'jinja'
|
||||
encoded2 = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded2["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded2["labels"])}')
|
||||
assert encoded['input_ids'][:-1] == encoded2['input_ids']
|
||||
|
||||
|
||||
def test_glm4_5():
|
||||
engine = TransformersEngine('ZhipuAI/GLM-4.5-Air')
|
||||
template = engine.template
|
||||
template.template_backend = 'jinja'
|
||||
_infer(engine, num_tools=2)
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
template.set_mode('train')
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
template.template_backend = 'jinja'
|
||||
encoded2 = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded2["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded2["labels"])}')
|
||||
assert encoded['input_ids'][:-1] == encoded2['input_ids']
|
||||
|
||||
|
||||
def test_glm4_7():
|
||||
engine = TransformersEngine('ZhipuAI/GLM-4.7-FP8', load_model=False)
|
||||
template = engine.template
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
template.set_mode('train')
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
template.template_backend = 'jinja'
|
||||
encoded2 = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded2["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded2["labels"])}')
|
||||
assert encoded['input_ids'][:-1] == encoded2['input_ids']
|
||||
|
||||
|
||||
def test_qwen3_coder():
|
||||
engine = TransformersEngine('Qwen/Qwen3-Coder-30B-A3B-Instruct')
|
||||
template = engine.template
|
||||
template.template_backend = 'jinja'
|
||||
_infer(engine, num_tools=2)
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
template.set_mode('train')
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
template.template_backend = 'jinja'
|
||||
encoded2 = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded2["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded2["labels"])}')
|
||||
assert encoded['input_ids'] == encoded2['input_ids']
|
||||
|
||||
|
||||
def test_qwen3_5():
|
||||
engine = TransformersEngine('Qwen/Qwen3.5-35B-A3B')
|
||||
template = engine.template
|
||||
template.template_backend = 'jinja'
|
||||
_infer(engine, num_tools=2)
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
data['messages'].insert(0, {'role': 'system', 'content': 'You are a helpful assistant.'})
|
||||
template.template_backend = 'swift'
|
||||
template.set_mode('train')
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
template.template_backend = 'jinja'
|
||||
encoded2 = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded2["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded2["labels"])}')
|
||||
assert encoded['input_ids'] == encoded2['input_ids']
|
||||
|
||||
|
||||
def test_deepseek_v3_1():
|
||||
engine = TransformersEngine('deepseek-ai/DeepSeek-V3.1', load_model=False)
|
||||
template = engine.template
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
# To test multiple tool calls and responses, we duplicate some messages.
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
template.set_mode('train')
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
template.template_backend = 'jinja'
|
||||
encoded2 = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded2["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded2["labels"])}')
|
||||
|
||||
expected_input_ids = (
|
||||
'<|begin▁of▁sentence|>\n\n## Tools\n'
|
||||
'You have access to the following tools:\n\n'
|
||||
'### convert_temperature\n'
|
||||
'Description: Convert temperature from one unit to another\n\n'
|
||||
"Parameters: {\"type\": \"object\", \"properties\": {\"temperature\": {\"type\": \"number\", "
|
||||
"\"description\": \"The temperature value\"}, \"from_unit\": {\"type\": \"string\", \"description\": "
|
||||
"\"The unit to convert from\"}, \"to_unit\": {\"type\": \"string\", \"description\": \"The unit "
|
||||
"to convert to\"}}, \"required\": [\"temperature\", \"from_unit\", \"to_unit\"]}\n\n"
|
||||
'### get_current_date\n'
|
||||
'Description: Get the current date\n\n'
|
||||
'Parameters: {}\n\n'
|
||||
'IMPORTANT: ALWAYS adhere to this exact format for tool use:\n'
|
||||
'<|tool▁calls▁begin|><|tool▁call▁begin|>tool_call_name<|tool▁sep|>tool_call_arguments<|tool▁call▁end|>'
|
||||
'{additional_tool_calls}<|tool▁calls▁end|>\n\n'
|
||||
'Where:\n'
|
||||
'- `tool_call_name` must be an exact match to one of the available tools\n'
|
||||
"- `tool_call_arguments` must be valid JSON that strictly follows the tool's Parameters Schema\n"
|
||||
'- For multiple tool calls, chain them directly without separators or spaces<|User|>'
|
||||
'Hi, I need to convert a temperature from Celsius to Fahrenheit. The temperature is 30 degrees Celsius.'
|
||||
'<|Assistant|></think><|tool▁calls▁begin|><|tool▁call▁begin|>convert_temperature<|tool▁sep|>'
|
||||
"{\"temperature\": 30, \"from_unit\": \"Celsius\", \"to_unit\": \"Fahrenheit\"}<|tool▁call▁end|>"
|
||||
'<|tool▁call▁begin|>convert_temperature<|tool▁sep|>'
|
||||
"{\"temperature\": 30, \"from_unit\": \"Celsius\", \"to_unit\": \"Fahrenheit\"}<|tool▁call▁end|>"
|
||||
'<|tool▁calls▁end|><|end▁of▁sentence|>'
|
||||
"<|tool▁output▁begin|>{\"converted_temperature\": 86}<|tool▁output▁end|>"
|
||||
"<|tool▁output▁begin|>{\"converted_temperature\": 86}<|tool▁output▁end|>"
|
||||
'The converted temperature from 30 degrees Celsius to Fahrenheit is 86 degrees Fahrenheit.<|end▁of▁sentence|>')
|
||||
|
||||
# Expected labels string
|
||||
expected_labels = (
|
||||
'[-100 * 239]</think><|tool▁calls▁begin|><|tool▁call▁begin|>convert_temperature<|tool▁sep|>'
|
||||
"{\"temperature\": 30, \"from_unit\": \"Celsius\", \"to_unit\": \"Fahrenheit\"}<|tool▁call▁end|>"
|
||||
'<|tool▁call▁begin|>convert_temperature<|tool▁sep|>'
|
||||
"{\"temperature\": 30, \"from_unit\": \"Celsius\", \"to_unit\": \"Fahrenheit\"}<|tool▁call▁end|>"
|
||||
'<|tool▁calls▁end|><|end▁of▁sentence|>[-100 * 22]'
|
||||
'The converted temperature from 30 degrees Celsius to Fahrenheit is 86 degrees Fahrenheit.<|end▁of▁sentence|>')
|
||||
|
||||
assert template.safe_decode(encoded['input_ids']) == expected_input_ids
|
||||
assert template.safe_decode(encoded['labels']) == expected_labels
|
||||
assert encoded['input_ids'][-122:] == encoded2['input_ids'][1:]
|
||||
|
||||
|
||||
def test_youtu():
|
||||
agent_template = agent_template_map['youtu']()
|
||||
new_system = agent_template._format_tools(tools, system)
|
||||
assert len(new_system) == 883
|
||||
engine = TransformersEngine('Tencent-YouTu-Research/Youtu-LLM-2B')
|
||||
template = engine.template
|
||||
template._agent_template = 'youtu'
|
||||
|
||||
stop = [template.agent_template.keyword.observation]
|
||||
query = "How's the weather in Beijing today?"
|
||||
tool_messages = [{'role': 'tool', 'content': '{"temperature": 32, "condition": "Sunny", "humidity": 50}'}]
|
||||
infer_request = InferRequest([{'role': 'user', 'content': query}], tools=tools)
|
||||
request_config = RequestConfig(max_tokens=2048, stop=stop, temperature=0)
|
||||
|
||||
# First inference: get tool call
|
||||
resp_list = engine.infer([infer_request], request_config=request_config)
|
||||
response = resp_list[0].choices[0].message.content
|
||||
toolcall = resp_list[0].choices[0].message.tool_calls
|
||||
print(f'response: {response}')
|
||||
print(f'toolcall: {toolcall}')
|
||||
assert toolcall is not None, 'No tool_call generated'
|
||||
infer_request.messages.append({'role': 'assistant', 'content': response})
|
||||
infer_request.messages += tool_messages
|
||||
|
||||
# Second inference: get final response
|
||||
resp_list = engine.infer([infer_request], request_config=request_config)
|
||||
response2 = resp_list[0].choices[0].message.content
|
||||
print(f'response2: {response2}')
|
||||
infer_request.messages.append({'role': 'assistant', 'content': response2})
|
||||
messages = infer_request.messages
|
||||
|
||||
template.set_mode('train')
|
||||
encoded = template.encode({'messages': messages})
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
template.template_backend = 'swift'
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
template.template_backend = 'jinja'
|
||||
encoded2 = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded2["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded2["labels"])}')
|
||||
assert encoded['input_ids'] == encoded2['input_ids']
|
||||
|
||||
|
||||
def test_deepseek_v4():
|
||||
engine = TransformersEngine('deepseek-ai/DeepSeek-V4-Flash', load_model=False)
|
||||
template = engine.template
|
||||
|
||||
tools = [{
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'get_weather',
|
||||
'description': 'Get the weather for a specific location',
|
||||
'parameters': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'location': {
|
||||
'type': 'string',
|
||||
'description': 'The city name'
|
||||
},
|
||||
'unit': {
|
||||
'type': 'string',
|
||||
'enum': ['celsius', 'fahrenheit'],
|
||||
'description': 'Temperature unit'
|
||||
}
|
||||
},
|
||||
'required': ['location']
|
||||
}
|
||||
}
|
||||
}, {
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'search',
|
||||
'description': 'Search the web for information',
|
||||
'parameters': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'query': {
|
||||
'type': 'string',
|
||||
'description': 'Search query'
|
||||
},
|
||||
'num_results': {
|
||||
'type': 'integer',
|
||||
'description': 'Number of results to return'
|
||||
}
|
||||
},
|
||||
'required': ['query']
|
||||
}
|
||||
}
|
||||
}]
|
||||
data = {
|
||||
'tools':
|
||||
tools,
|
||||
'messages': [{
|
||||
'role': 'system',
|
||||
'content': 'You are a helpful assistant.'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': "What's the weather in Beijing?"
|
||||
}, {
|
||||
'role':
|
||||
'assistant',
|
||||
'content':
|
||||
'<think>The user wants to know the weather in Beijing. I should use the get_weather tool.</think>\n\n'
|
||||
}, {
|
||||
'role':
|
||||
'tool_call',
|
||||
'content':
|
||||
'{"name": "get_weather", "arguments": "{\\"location\\": \\"Beijing\\", \\"unit\\": \\"celsius\\"}"}'
|
||||
}, {
|
||||
'role': 'tool_response',
|
||||
'content': '{"temperature": 22, "condition": "sunny", "humidity": 45}'
|
||||
}, {
|
||||
'role':
|
||||
'assistant',
|
||||
'content': ('<think>Got the weather data. Let me format a nice response.</think>'
|
||||
'The weather in Beijing is currently sunny with a temperature of 22°C and 45% humidity.')
|
||||
}]
|
||||
}
|
||||
|
||||
template.template_backend = 'swift'
|
||||
template.set_mode('train')
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
|
||||
expected_input_ids = (
|
||||
'<|begin▁of▁sentence|>You are a helpful assistant.\n\n## Tools\n\n'
|
||||
'You have access to a set of tools to help answer the user\'s question. '
|
||||
'You can invoke tools by writing a "<|DSML|tool_calls>" block like the following:\n\n'
|
||||
'<|DSML|tool_calls>\n'
|
||||
'<|DSML|invoke name="$TOOL_NAME">\n'
|
||||
'<|DSML|parameter name="$PARAMETER_NAME" string="true|false">$PARAMETER_VALUE</|DSML|parameter>\n'
|
||||
'...\n'
|
||||
'</|DSML|invoke>\n'
|
||||
'<|DSML|invoke name="$TOOL_NAME2">\n'
|
||||
'...\n'
|
||||
'</|DSML|invoke>\n'
|
||||
'</|DSML|tool_calls>\n\n'
|
||||
'String parameters should be specified as is and set `string="true"`. '
|
||||
'For all other types (numbers, booleans, arrays, objects), '
|
||||
'pass the value in JSON format and set `string="false"`.\n\n'
|
||||
'If thinking_mode is enabled (triggered by <think>), '
|
||||
'you MUST output your complete reasoning inside <think>...</think> BEFORE any tool calls or final response.'
|
||||
'\n\nOtherwise, output directly after </think> with tool calls or final response.\n\n'
|
||||
'### Available Tool Schemas\n\n'
|
||||
'{"name": "get_weather", "description": "Get the weather for a specific location", '
|
||||
'"parameters": {"type": "object", "properties": {"location": {"type": "string", '
|
||||
'"description": "The city name"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], '
|
||||
'"description": "Temperature unit"}}, "required": ["location"]}}\n'
|
||||
'{"name": "search", "description": "Search the web for information", '
|
||||
'"parameters": {"type": "object", "properties": {"query": {"type": "string", '
|
||||
'"description": "Search query"}, "num_results": {"type": "integer", '
|
||||
'"description": "Number of results to return"}}, "required": ["query"]}}\n\n'
|
||||
'You MUST strictly follow the above defined tool name and parameter schemas to invoke tool calls.\n'
|
||||
'<|User|>What\'s the weather in Beijing?<|Assistant|>'
|
||||
'<think>The user wants to know the weather in Beijing. I should use the get_weather tool.</think>\n\n'
|
||||
'<|DSML|tool_calls>\n'
|
||||
'<|DSML|invoke name="get_weather">\n'
|
||||
'<|DSML|parameter name="location" string="true">Beijing</|DSML|parameter>\n'
|
||||
'<|DSML|parameter name="unit" string="true">celsius</|DSML|parameter>\n'
|
||||
'</|DSML|invoke>\n'
|
||||
'</|DSML|tool_calls>'
|
||||
'<|end▁of▁sentence|>'
|
||||
'<|User|><tool_result>{"temperature": 22, "condition": "sunny", "humidity": 45}</tool_result>'
|
||||
'<|Assistant|>'
|
||||
'<think>Got the weather data. Let me format a nice response.</think>'
|
||||
'The weather in Beijing is currently sunny with a temperature of 22°C and 45% humidity.'
|
||||
'<|end▁of▁sentence|>')
|
||||
|
||||
assert template.safe_decode(encoded['input_ids']) == expected_input_ids
|
||||
|
||||
|
||||
def test_seed_oss():
|
||||
engine = TransformersEngine('ByteDance-Seed/Seed-OSS-36B-Instruct', load_model=False)
|
||||
|
||||
template = engine.template
|
||||
dataset = load_dataset('AI-ModelScope/function-calling-chatml')[0]
|
||||
data = dataset[6]
|
||||
# To test multiple tool calls and responses, we duplicate some messages.
|
||||
data['messages'].insert(1, data['messages'][1])
|
||||
data['messages'].insert(3, data['messages'][3])
|
||||
|
||||
# Incomplete tool function will cause seed template to throw an error.
|
||||
data['tools'] = [('{\n'
|
||||
' "name": "convert_temperature",\n'
|
||||
' "description": "Convert temperature from one unit to another",\n'
|
||||
' "parameters": {\n'
|
||||
' "type": "object",\n'
|
||||
' "properties": {\n'
|
||||
' "temperature": {\n'
|
||||
' "type": "number",\n'
|
||||
' "description": "The temperature value"\n'
|
||||
' },\n'
|
||||
' "from_unit": {\n'
|
||||
' "type": "string",\n'
|
||||
' "description": "The unit to convert from"\n'
|
||||
' },\n'
|
||||
' "to_unit": {\n'
|
||||
' "type": "string",\n'
|
||||
' "description": "The unit to convert to"\n'
|
||||
' }\n'
|
||||
' },\n'
|
||||
' "required": [\n'
|
||||
' "temperature",\n'
|
||||
' "from_unit",\n'
|
||||
' "to_unit"\n'
|
||||
' ]\n'
|
||||
' }\n'
|
||||
'}'),
|
||||
('{\n'
|
||||
' "name": "get_current_date",\n'
|
||||
' "description": "Get the current date",\n'
|
||||
' "parameters": {\n'
|
||||
' "type": "object",\n'
|
||||
' "properties": {\n'
|
||||
' "date": {\n'
|
||||
' "type": "number",\n'
|
||||
' "description": "The date value"}}}\n'
|
||||
'}')]
|
||||
|
||||
data['thinking_budget'] = 0
|
||||
|
||||
template.template_backend = 'swift'
|
||||
template.set_mode('train')
|
||||
encoded = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded["labels"])}')
|
||||
import re
|
||||
expected_input_ids = re.sub(
|
||||
r'<seed:think>.*?</seed:think>', '', template.safe_decode(encoded['input_ids']), flags=re.DOTALL)
|
||||
template.template_backend = 'jinja'
|
||||
encoded2 = template.encode(data)
|
||||
print(f'input_ids: {template.safe_decode(encoded2["input_ids"])}')
|
||||
print(f'labels: {template.safe_decode(encoded2["labels"])}')
|
||||
assert template.safe_decode(encoded2['input_ids']) == expected_input_ids
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from swift import InferRequest, RequestConfig, TransformersEngine, agent_template_map, load_dataset
|
||||
|
||||
# test_react_en()
|
||||
# test_react_zh()
|
||||
# test_qwen_en()
|
||||
# test_qwen_zh()
|
||||
# test_qwen_en_parallel()
|
||||
# test_qwen_zh_parallel()
|
||||
# test_hermes()
|
||||
# test_toolbench()
|
||||
# test_chatglm4()
|
||||
# test_glm4()
|
||||
# test_llama3()
|
||||
# test_llama4()
|
||||
# test_hunyuan()
|
||||
# test_glm4_5()
|
||||
# test_glm4_7()
|
||||
# test_qwen3_coder()
|
||||
# test_qwen3_5()
|
||||
# test_deepseek_v3_1()
|
||||
test_deepseek_v4()
|
||||
# test_seed_oss()
|
||||
# test_youtu()
|
||||
@@ -0,0 +1,116 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
|
||||
|
||||
def _infer_model(engine, system=None, messages=None, audios=None):
|
||||
seed_everything(42)
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0)
|
||||
if messages is None:
|
||||
messages = []
|
||||
if system is not None:
|
||||
messages += [{'role': 'system', 'content': system}]
|
||||
messages += [{'role': 'user', 'content': '你好'}]
|
||||
resp = engine.infer([{'messages': messages}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}]
|
||||
messages += [{'role': 'user', 'content': '<audio>这段语音说了什么'}]
|
||||
else:
|
||||
messages = messages.copy()
|
||||
if audios is None:
|
||||
audios = ['http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/weather.wav']
|
||||
resp = engine.infer([{'messages': messages, 'audios': audios}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}]
|
||||
logger.info(f'model: {engine.model_info.model_name}, messages: {messages}')
|
||||
return response
|
||||
|
||||
|
||||
def test_qwen_audio():
|
||||
engine = TransformersEngine('Qwen/Qwen-Audio-Chat')
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_qwen2_audio():
|
||||
# transformers==4.48.3
|
||||
engine = TransformersEngine('Qwen/Qwen2-Audio-7B-Instruct')
|
||||
messages = [{'role': 'user', 'content': '<audio>'}]
|
||||
audios = ['https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2-Audio/audio/guess_age_gender.wav']
|
||||
response = _infer_model(engine, messages=messages, audios=audios)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, audios=audios)
|
||||
assert response == response2 == 'Yes, the speaker is female and in her twenties.'
|
||||
|
||||
|
||||
def test_xcomposer2d5_ol():
|
||||
engine = TransformersEngine('Shanghai_AI_Laboratory/internlm-xcomposer2d5-ol-7b:audio')
|
||||
_infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_step_audio_chat():
|
||||
engine = TransformersEngine('stepfun-ai/Step-Audio-Chat')
|
||||
response = _infer_model(engine, messages=[{'role': 'user', 'content': '<audio>'}])
|
||||
assert response == ('是的呢,今天天气晴朗,阳光明媚,微风和煦,非常适合外出活动。天空湛蓝,白云朵朵,让人心情愉悦。希望你能好好享受这美好的一天!')
|
||||
|
||||
|
||||
def test_qwen2_5_omni():
|
||||
USE_AUDIO_IN_VIDEO = True
|
||||
os.environ['USE_AUDIO_IN_VIDEO'] = str(USE_AUDIO_IN_VIDEO)
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-Omni-7B')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_gemma3n():
|
||||
engine = TransformersEngine('google/gemma-3n-E4B-it')
|
||||
messages = [{'role': 'user', 'content': '<audio>Transcribe this audio and complete the statement'}]
|
||||
audios = ['https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2-Audio/audio/guess_age_gender.wav']
|
||||
response = _infer_model(engine, messages=messages, audios=audios)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, audios=audios)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_midashenglm():
|
||||
engine = TransformersEngine('mispeech/midashenglm-7b')
|
||||
messages = [{'role': 'user', 'content': '<audio>Caption the audio.'}]
|
||||
response = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages)
|
||||
assert response == response2 == "The audio contains a male voice speaking the phrase '今天天气真好呀' in Mandarin."
|
||||
|
||||
|
||||
def test_step_audio2_mini():
|
||||
engine = TransformersEngine('stepfun-ai/Step-Audio-2-mini')
|
||||
messages = [{'role': 'user', 'content': '<audio>Caption the audio'}]
|
||||
response = _infer_model(engine, messages=messages)
|
||||
assert response == 'A woman says "今天天气真好呀" in Mandarin.'
|
||||
|
||||
|
||||
def test_qwen3_asr():
|
||||
messages = [{'role': 'user', 'content': '<audio>'}]
|
||||
audios = ['https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-ASR-Repo/asr_zh.wav']
|
||||
engine = TransformersEngine('Qwen/Qwen3-ASR-1.7B')
|
||||
engine.template._response_prefix = 'language Chinese<asr_text>'
|
||||
response = _infer_model(engine, messages=messages, audios=audios)
|
||||
assert response == 'language Chinese<asr_text>甚至出现交易几乎停滞的情况。'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from swift.infer_engine import RequestConfig, TransformersEngine
|
||||
from swift.utils import get_logger, seed_everything
|
||||
logger = get_logger()
|
||||
# test_qwen_audio()
|
||||
# test_qwen2_audio()
|
||||
# test_xcomposer2d5_ol()
|
||||
# test_step_audio_chat()
|
||||
# test_qwen2_5_omni()
|
||||
# test_gemma3n()
|
||||
# test_midashenglm()
|
||||
# test_step_audio2_mini()
|
||||
test_qwen3_asr()
|
||||
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['SWIFT_DEBUG'] = '1'
|
||||
|
||||
|
||||
def test_deepseek_janus_pro_gene():
|
||||
from swift import InferArguments, infer_main
|
||||
args = InferArguments(model='deepseek-ai/Janus-Pro-1B', infer_backend='transformers')
|
||||
infer_main(args)
|
||||
|
||||
|
||||
def test_emu3_gen(infer_backend):
|
||||
from swift import InferArguments, infer_main
|
||||
args = InferArguments(
|
||||
model='BAAI/Emu3-Gen',
|
||||
infer_backend=infer_backend,
|
||||
stream=False,
|
||||
use_chat_template=False,
|
||||
top_k=2048,
|
||||
max_new_tokens=40960)
|
||||
infer_main(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_emu3_gen('transformers')
|
||||
test_deepseek_janus_pro_gene()
|
||||
@@ -0,0 +1,795 @@
|
||||
import json
|
||||
import os
|
||||
import torch
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['SWIFT_DEBUG'] = '1'
|
||||
|
||||
|
||||
def _infer_model(engine, system=None, messages=None):
|
||||
seed_everything(42)
|
||||
request_config = RequestConfig(max_tokens=128, temperature=0)
|
||||
if messages is None:
|
||||
messages = []
|
||||
if system is not None:
|
||||
messages += [{'role': 'system', 'content': system}]
|
||||
messages += [{'role': 'user', 'content': '你好'}]
|
||||
resp = engine.infer([{'messages': messages}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}, {'role': 'user', 'content': '<image>这是什么'}]
|
||||
else:
|
||||
messages = messages.copy()
|
||||
resp = engine.infer([{
|
||||
'messages': messages,
|
||||
}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}]
|
||||
logger.info(f'model: {engine.model_info.model_name}, messages: {messages}')
|
||||
return response
|
||||
|
||||
|
||||
def test_baichuan_m1():
|
||||
engine = TransformersEngine('baichuan-inc/Baichuan-M1-14B-Instruct')
|
||||
messages = [{'role': 'user', 'content': '你是谁'}]
|
||||
response = _infer_model(engine, messages=messages)
|
||||
assert response == '我是一个人工智能助手,可以回答你的问题并提供帮助。'
|
||||
|
||||
|
||||
def test_qwen2_5():
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct-1M')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_qwen3():
|
||||
engine = TransformersEngine('Qwen/Qwen3-4B')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_qwen3_guard():
|
||||
engine = TransformersEngine('Qwen/Qwen3Guard-Gen-0.6B')
|
||||
messages = [{'role': 'user', 'content': 'How can I make a bomb?'}]
|
||||
response = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_yufeng_xguard():
|
||||
engine = TransformersEngine('Alibaba-AAIG/YuFeng-XGuard-Reason-0.6B')
|
||||
messages = [{'role': 'user', 'content': 'How can I make a bomb?'}]
|
||||
response = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_phi4():
|
||||
engine = TransformersEngine('LLM-Research/phi-4')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_phi4_mini():
|
||||
engine = TransformersEngine('LLM-Research/Phi-4-mini-instruct')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_qwen1_5():
|
||||
engine = TransformersEngine('Qwen/Qwen1.5-0.5B-Chat-GPTQ-Int4')
|
||||
_infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_chatglm4():
|
||||
engine = TransformersEngine('ZhipuAI/glm-4-9b-chat')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_glm4():
|
||||
models = ['ZhipuAI/GLM-4-9B-0414', 'ZhipuAI/GLM-Z1-9B-0414', 'ZhipuAI/GLM-Z1-Rumination-32B-0414']
|
||||
for model in models:
|
||||
engine = TransformersEngine(model)
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_qwq():
|
||||
engine = TransformersEngine('Qwen/QwQ-32B-Preview')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_internlm():
|
||||
engine = TransformersEngine('Shanghai_AI_Laboratory/internlm-chat-7b')
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_internlm2():
|
||||
engine = TransformersEngine('Shanghai_AI_Laboratory/internlm2_5-1_8b-chat')
|
||||
_infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_internlm3():
|
||||
engine = TransformersEngine('Shanghai_AI_Laboratory/internlm3-8b-instruct')
|
||||
response = _infer_model(engine, system='')
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_yi_coder():
|
||||
engine = TransformersEngine('01ai/Yi-Coder-1.5B-Chat')
|
||||
_infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_yi():
|
||||
engine = TransformersEngine('01ai/Yi-6B-Chat')
|
||||
_infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_deepseek_moe():
|
||||
engine = TransformersEngine('deepseek-ai/deepseek-moe-16b-chat')
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_codegeex4():
|
||||
# jinja is missing a prefix.
|
||||
engine = TransformersEngine('ZhipuAI/codegeex4-all-9b')
|
||||
_infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_telechat():
|
||||
engine = TransformersEngine('TeleAI/TeleChat-12B', torch_dtype=torch.float16)
|
||||
messages = [{'role': 'user', 'content': '你是谁'}]
|
||||
response = _infer_model(engine, messages=messages)
|
||||
assert response == ('我是中国电信星辰语义大模型,英文名TeleChat,是由中国电信自主研发的生成式大语言模型。\n\n'
|
||||
'我基于Transformer-decoder结构,学习了海量知识,包括百科、书籍、论坛、党政媒体、GitHub代码、专业领域知识等,'
|
||||
'具备自然语言处理、语义理解、内容创作和逻辑推理等能力,可以与人类进行对话互动和情感交流,还能提供知识问答、创作写作、'
|
||||
'代码生成等服务,希望能为人类带来更加智能、高效和便捷的工作与生活体验。')
|
||||
|
||||
|
||||
def test_telechat2():
|
||||
engine = TransformersEngine('TeleAI/TeleChat2-7B-32K', torch_dtype=torch.float16)
|
||||
messages = [{'role': 'system', 'content': '你是一个乐于助人的智能助手,请使用用户提问的语言进行有帮助的问答'}, {'role': 'user', 'content': '你好'}]
|
||||
response = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_glm_edge():
|
||||
engine = TransformersEngine('ZhipuAI/glm-edge-1.5b-chat')
|
||||
_infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_llama():
|
||||
from swift.infer_engine import VllmEngine
|
||||
|
||||
# engine = TransformersEngine('LLM-Research/Meta-Llama-3.1-8B-Instruct-BNB-NF4')
|
||||
# engine = TransformersEngine('LLM-Research/Meta-Llama-3.1-8B-Instruct')
|
||||
# engine = TransformersEngine('LLM-Research/Meta-Llama-3-8B-Instruct')
|
||||
engine = VllmEngine('LLM-Research/Llama-3.2-1B-Instruct')
|
||||
# engine = TransformersEngine('AI-ModelScope/Llama-3.1-Nemotron-70B-Instruct-HF')
|
||||
# engine = TransformersEngine('unsloth/Llama-3.3-70B-Instruct-bnb-4bit')
|
||||
|
||||
res = _infer_model(engine, system='')
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, system='')
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_openbuddy():
|
||||
# engine = TransformersEngine('OpenBuddy/openbuddy-yi1.5-34b-v21.3-32k')
|
||||
engine = TransformersEngine('OpenBuddy/openbuddy-nemotron-70b-v23.2-131k')
|
||||
# engine = TransformersEngine('OpenBuddy/openbuddy-llama3.3-70b-v24.3-131k')
|
||||
res = _infer_model(engine, system='')
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_megrez():
|
||||
engine = TransformersEngine('InfiniAI/Megrez-3b-Instruct')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_skywork_o1():
|
||||
engine = TransformersEngine('AI-ModelScope/Skywork-o1-Open-Llama-3.1-8B')
|
||||
res = _infer_model(
|
||||
engine,
|
||||
messages=[{
|
||||
'role':
|
||||
'user',
|
||||
'content':
|
||||
('Jane has 12 apples. She gives 4 apples to her friend Mark, then buys 1 more apple, and finally splits '
|
||||
'all her apples equally among herself and her 2 siblings. How many apples does each person get?')
|
||||
}])
|
||||
assert res == ("To solve the problem, let's break it down into a series of logical steps:\n\n1. **Initial Number "
|
||||
'of Apples**: Jane starts with 12 apples.\n2. **Apples Given Away**: Jane gives 4 apples to her '
|
||||
'friend Mark. So, the number of apples she has now is:\n \\[\n 12 - 4 = 8\n \\]\n3. **Apples '
|
||||
'Bought**: Jane then buys 1 more apple. So, the number of apples she has now is:\n \\[\n '
|
||||
'8 + 1 = 9\n \\]\n4. **Apples Split Equally')
|
||||
|
||||
|
||||
def test_internlm2_reward():
|
||||
engine = TransformersEngine('Shanghai_AI_Laboratory/internlm2-1_8b-reward')
|
||||
messages = [{
|
||||
'role': 'user',
|
||||
'content': "Hello! What's your name?"
|
||||
}, {
|
||||
'role': 'assistant',
|
||||
'content': 'My name is InternLM2! A helpful AI assistant. What can I do for you?'
|
||||
}]
|
||||
res = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, messages=messages)
|
||||
assert res == res2 == '0.48681640625'
|
||||
|
||||
|
||||
def test_qwen2_reward():
|
||||
engine = TransformersEngine('Qwen/Qwen2-Math-RM-72B')
|
||||
messages = [{
|
||||
'role':
|
||||
'user',
|
||||
'content': ('Suppose that a certain software product has a mean time between failures of 10,000 hours '
|
||||
'and has a mean time to repair of 20 hours. If the product is used by 100 customers, '
|
||||
'what is its availability?\nAnswer Choices: (A) 80% (B) 90% (C) 98% (D) 99.80%\nPlease '
|
||||
'reason step by step, and put your final answer within \\boxed{}.')
|
||||
}, {
|
||||
'role':
|
||||
'assistant',
|
||||
'content': ("To find the availability of the software product, we'll use the formula:\n\n\\[ \\text{ "
|
||||
'availability} = \\frac{\\text{Mean Time Between Failures (MTBF)}}{\\text{Mean Time Between '
|
||||
'Failures (MTBF) + Mean Time To Repair (MTTR)}} \\]\n\nGiven:\n- MTBF = 10,000 hours\n- MTTR '
|
||||
"= 20 hours\n\nLet's plug these values into the formula:\n\n\\[ \\text{availability} = "
|
||||
'\\frac{10,000}{10,000 + 20} = \\frac{10,000}{10,020} \\]\n\nTo simplify this fraction, '
|
||||
'we can divide both the numerator and the denominator by 10,000:\n\n\\[ \\text{availability} ='
|
||||
' \\frac{10,000 \\div 10,000}{10,020 \\div 10,000} = \\frac{1}{1.002} \\]\n\nTo express this as'
|
||||
' a percentage, we can calculate the decimal value of the fraction and then multiply by '
|
||||
'100:\n\n\\[ \\text{availability} \\approx 0.998002 \\times 100 = 99.80\\% \\]\n\nTherefore, '
|
||||
'the availability of the software product is approximately 99.80%.\n\nThe correct answer is '
|
||||
'\\boxed{D}')
|
||||
}]
|
||||
res = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, messages=messages)
|
||||
assert res == '1.84375' and res2 == '1.390625' # \n diff
|
||||
|
||||
|
||||
def test_qwen2_5_math():
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-Math-1.5B-Instruct')
|
||||
messages = [{'role': 'user', 'content': 'Find the value of $x$ that satisfies the equation $4x+5 = 6x+7$.'}]
|
||||
res = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, messages=messages)
|
||||
assert res == res2
|
||||
|
||||
|
||||
def test_skywork_reward():
|
||||
prompt = ('Jane has 12 apples. She gives 4 apples to her friend Mark, then buys 1 more apple, and finally splits '
|
||||
'all her apples equally among herself and her 2 siblings. How many apples does each person get?')
|
||||
response = ('1. Jane starts with 12 apples and gives 4 to Mark. 12 - 4 = 8. Jane now has 8 apples.\n2. Jane buys '
|
||||
'1 more apple. 8 + 1 = 9. Jane now has 9 apples.\n3. Jane splits the 9 apples equally among herself '
|
||||
'and her 2 siblings (3 people in total). 9 ÷ 3 = 3 apples each. Each person gets 3 apples.')
|
||||
|
||||
engine = TransformersEngine('AI-ModelScope/Skywork-Reward-Llama-3.1-8B-v0.2')
|
||||
messages = [{'role': 'user', 'content': prompt}, {'role': 'assistant', 'content': response}]
|
||||
res = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, messages=messages)
|
||||
assert res == '14.25'
|
||||
assert res2 == '13.8125'
|
||||
|
||||
|
||||
def test_deepseek_r1_distill():
|
||||
engine = TransformersEngine('deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_deepseek_prover_v2():
|
||||
engine = TransformersEngine('deepseek-ai/DeepSeek-Prover-V2-7B')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_qwen2_5_prm():
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-Math-7B-PRM800K')
|
||||
data = {
|
||||
'system':
|
||||
'Please reason step by step, and put your final answer within \\boxed{}.',
|
||||
'query': ('Sue lives in a fun neighborhood. One weekend, the neighbors decided to play a prank on Sue. '
|
||||
"On Friday morning, the neighbors placed 18 pink plastic flamingos out on Sue's front yard. "
|
||||
'On Saturday morning, the neighbors took back one third of the flamingos, painted them white, and '
|
||||
"put these newly painted white flamingos back out on Sue's front yard. Then, on Sunday morning, "
|
||||
'they added another 18 pink plastic flamingos to the collection. At noon on Sunday, how many more '
|
||||
'pink plastic flamingos were out than white plastic flamingos?'),
|
||||
'response':
|
||||
[('To find out how many more pink plastic flamingos were out than white plastic flamingos at noon on Sunday, '
|
||||
'we can break down the problem into steps. First, on Friday, the neighbors start with 18 pink '
|
||||
'plastic flamingos.'),
|
||||
('On Saturday, they take back one third of the flamingos. Since there were 18 flamingos, (1/3 \\times 18 = 6) '
|
||||
'flamingos are taken back. So, they have (18 - 6 = 12) flamingos left in their possession. Then, they paint '
|
||||
"these 6 flamingos white and put them back out on Sue's front yard. Now, Sue has the original 12 pink "
|
||||
'flamingos plus the 6 new white ones. Thus, by the end of Saturday, Sue has (12 + 6 = 18) pink flamingos '
|
||||
'and 6 white flamingos.'),
|
||||
("On Sunday, the neighbors add another 18 pink plastic flamingos to Sue's front yard. By the end of Sunday "
|
||||
'morning, Sue has (18 + 18 = 36) pink flamingos and still 6 white flamingos.'),
|
||||
('To find the difference, subtract the number of white flamingos from the number of pink '
|
||||
'flamingos: (36 - 6 = 30). Therefore, at noon on Sunday, there were 30 more pink plastic flamingos out '
|
||||
'than white plastic flamingos. The answer is (\\boxed{30}).')]
|
||||
}
|
||||
|
||||
messages = [
|
||||
{
|
||||
'role': 'system',
|
||||
'content': data['system']
|
||||
},
|
||||
{
|
||||
'role': 'user',
|
||||
'content': data['query']
|
||||
},
|
||||
{
|
||||
'role': 'assistant',
|
||||
'content': '<extra_0>'.join(data['response']) + '<extra_0>'
|
||||
},
|
||||
]
|
||||
res = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, messages=messages)
|
||||
assert res == res2 == json.dumps([0.9921875, 0.2490234375, 0.70703125, 0.9375]), f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_mistral_small():
|
||||
engine = TransformersEngine('mistralai/Mistral-Small-24B-Instruct-2501')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_moonlight():
|
||||
engine = TransformersEngine('moonshotai/Moonlight-16B-A3B-Instruct')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_ling():
|
||||
engine = TransformersEngine('inclusionAI/Ling-lite')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_gemma3():
|
||||
engine = TransformersEngine('LLM-Research/gemma-3-1b-it')
|
||||
res = _infer_model(engine, system='You are a helpful assistant')
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, system='You are a helpful assistant')
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_mimo():
|
||||
engine = TransformersEngine('XiaomiMiMo/MiMo-7B-RL-0530')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_minicpm():
|
||||
engine = TransformersEngine('OpenBMB/MiniCPM4-0.5B')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_minimax():
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2,3,4,5,6,7'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1,2,3,4,5,6,7'
|
||||
from transformers import QuantoConfig
|
||||
quantization_config = QuantoConfig(weights='int8')
|
||||
messages = [{
|
||||
'role': 'system',
|
||||
'content': 'You are a helpful assistant.'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'who are you?'
|
||||
}]
|
||||
engine = TransformersEngine('MiniMax/MiniMax-M1-40k', quantization_config=quantization_config)
|
||||
res = _infer_model(engine, messages=messages)
|
||||
print(f'res: {res}')
|
||||
|
||||
|
||||
def test_kimi_dev():
|
||||
engine = TransformersEngine('moonshotai/Kimi-Dev-72B')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_hunyuan():
|
||||
# engine = TransformersEngine('Tencent-Hunyuan/Hunyuan-A13B-Instruct')
|
||||
engine = TransformersEngine('Tencent-Hunyuan/Hunyuan-4B-Instruct')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_ernie():
|
||||
engine = TransformersEngine('PaddlePaddle/ERNIE-4.5-0.3B-PT')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_devstral():
|
||||
from swift.template.templates.mistral import devstral_small_2505_system
|
||||
|
||||
engine = TransformersEngine('mistralai/Devstral-Small-2505')
|
||||
res = _infer_model(engine, system=devstral_small_2505_system)
|
||||
|
||||
engine.template.template_backend = 'jinja'
|
||||
# taken from: https://github.com/vllm-project/vllm/blob/main/examples/tool_chat_template_mistral3.jinja
|
||||
chat_template = (
|
||||
'{%- set today = strftime_now("%Y-%m-%d") %}\n'
|
||||
'{%- set default_system_message = "You are Mistral Small 3, a Large Language Model (LLM) '
|
||||
'created by Mistral AI, a French startup headquartered in Paris.\\nYour knowledge base was '
|
||||
'last updated on 2023-10-01. The current date is " + today + ".\\n\\nWhen you\'re not sure '
|
||||
'about some information, you say that you don\'t have the information and don\'t make up '
|
||||
'anything.\\nIf the user\'s question is not clear, ambiguous, or does not provide enough '
|
||||
'context for you to accurately answer the question, you do not try to answer it right away '
|
||||
'and you rather ask the user to clarify their request (e.g. \\"What are some good restaurants '
|
||||
'around me?\\" => \\"Where are you?\\" or \\"When is the next flight to Tokyo\\" => '
|
||||
'\\"Where do you travel from?\\")" %}\n\n'
|
||||
'{{- bos_token }}\n\n'
|
||||
'{%- if messages[0][\'role\'] == \'system\' %}\n'
|
||||
' {%- if messages[0][\'content\'] is string %}\n'
|
||||
' {%- set system_message = messages[0][\'content\'] %}\n'
|
||||
' {%- set loop_messages = messages[1:] %}\n'
|
||||
' {%- else %}\n'
|
||||
' {%- set system_message = messages[0][\'content\'][0][\'text\'] %}\n'
|
||||
' {%- set loop_messages = messages[1:] %}\n'
|
||||
' {%- endif %}\n'
|
||||
'{%- else %}\n'
|
||||
' {%- set system_message = default_system_message %}\n'
|
||||
' {%- set loop_messages = messages %}\n'
|
||||
'{%- endif %}\n'
|
||||
'{%- if not tools is defined %}\n'
|
||||
' {%- set tools = none %}\n'
|
||||
'{%- elif tools is not none %}\n'
|
||||
' {%- set parallel_tool_prompt = "You are a helpful assistant that can call tools. '
|
||||
'If you call one or more tools, format them in a single JSON array or objects, where each '
|
||||
'object is a tool call, not as separate objects outside of an array or multiple arrays. '
|
||||
'Use the format [{\\"name\\": tool call name, \\"arguments\\": tool call arguments}, '
|
||||
'additional tool calls] if you call more than one tool. If you call tools, do not attempt '
|
||||
'to interpret them or otherwise provide a response until you receive a tool call result '
|
||||
'that you can interpret for the user." %}\n'
|
||||
' {%- if system_message is defined %}\n'
|
||||
' {%- set system_message = parallel_tool_prompt + "\\n\\n" + system_message %}\n'
|
||||
' {%- else %}\n'
|
||||
' {%- set system_message = parallel_tool_prompt %}\n'
|
||||
' {%- endif %}\n'
|
||||
'{%- endif %}\n'
|
||||
'{{- \'[SYSTEM_PROMPT]\' + system_message + \'[/SYSTEM_PROMPT]\' }}\n\n'
|
||||
'{%- set user_messages = loop_messages | selectattr("role", "equalto", "user") | list %}\n\n'
|
||||
'{%- set filtered_messages = [] %}\n'
|
||||
'{%- for message in loop_messages %}\n'
|
||||
' {%- if message["role"] not in ["tool", "tool_results"] and not message.get("tool_calls") %}\n'
|
||||
' {%- set filtered_messages = filtered_messages + [message] %}\n'
|
||||
' {%- endif %}\n'
|
||||
'{%- endfor %}\n\n'
|
||||
'{%- for message in filtered_messages %}\n'
|
||||
' {%- if (message["role"] == "user") != (loop.index0 % 2 == 0) %}\n'
|
||||
' {{- raise_exception("After the optional system message, conversation roles must '
|
||||
'alternate user/assistant/user/assistant/...") }}\n'
|
||||
' {%- endif %}\n'
|
||||
'{%- endfor %}\n\n'
|
||||
'{%- for message in loop_messages %}\n'
|
||||
' {%- if message["role"] == "user" %}\n'
|
||||
' {%- if tools is not none and (message == user_messages[-1]) %}\n'
|
||||
' {{- "[AVAILABLE_TOOLS] [" }}\n'
|
||||
' {%- for tool in tools %}\n'
|
||||
' {%- set tool = tool.function %}\n'
|
||||
' {{- \'{"type": "function", "function": {\' }}\n'
|
||||
' {%- for key, val in tool.items() if key != "return" %}\n'
|
||||
' {%- if val is string %}\n'
|
||||
' {{- \'"\' + key + \'": "\' + val + \'"\' }}\n'
|
||||
' {%- else %}\n'
|
||||
' {{- \'"\' + key + \'": \' + val|tojson }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {%- if not loop.last %}\n'
|
||||
' {{- ", " }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {%- endfor %}\n'
|
||||
' {{- "}}" }}\n'
|
||||
' {%- if not loop.last %}\n'
|
||||
' {{- ", " }}\n'
|
||||
' {%- else %}\n'
|
||||
' {{- "]" }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {%- endfor %}\n'
|
||||
' {{- "[/AVAILABLE_TOOLS]" }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {%- if message[\'content\'] is string %}\n'
|
||||
' {{- \'[INST]\' + message[\'content\'] + \'[/INST]\' }}\n'
|
||||
' {%- else %}\n'
|
||||
' {{- \'[INST]\' }}\n'
|
||||
' {%- for block in message[\'content\'] %}\n'
|
||||
' {%- if block[\'type\'] == \'text\' %}\n'
|
||||
' {{- block[\'text\'] }}\n'
|
||||
' {%- elif block[\'type\'] == \'image\' or block[\'type\'] == \'image_url\' %}\n'
|
||||
' {{- \'[IMG]\' }}\n'
|
||||
' {%- else %}\n'
|
||||
' {{- raise_exception(\'Only text and image blocks are supported '
|
||||
'in message content!\') }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {%- endfor %}\n'
|
||||
' {{- \'[/INST]\' }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {%- elif message["role"] == "tool_calls" or message.tool_calls is defined %}\n'
|
||||
' {%- if message.tool_calls is defined %}\n'
|
||||
' {%- set tool_calls = message.tool_calls %}\n'
|
||||
' {%- else %}\n'
|
||||
' {%- set tool_calls = message.content %}\n'
|
||||
' {%- endif %}\n'
|
||||
' {{- "[TOOL_CALLS] [" }}\n'
|
||||
' {%- for tool_call in tool_calls %}\n'
|
||||
' {%- set out = tool_call.function|tojson %}\n'
|
||||
' {{- out[:-1] }}\n'
|
||||
' {%- if not tool_call.id is defined or tool_call.id|length < 9 %}\n'
|
||||
' {{- raise_exception("Tool call IDs should be alphanumeric strings with '
|
||||
'length >= 9! (1)" + tool_call.id) }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {{- \', "id": "\' + tool_call.id[-9:] + \'"}\' }}\n'
|
||||
' {%- if not loop.last %}\n'
|
||||
' {{- ", " }}\n'
|
||||
' {%- else %}\n'
|
||||
' {{- "]" + eos_token }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {%- endfor %}\n'
|
||||
' {%- elif message[\'role\'] == \'assistant\' %}\n'
|
||||
' {%- if message[\'content\'] is string %}\n'
|
||||
' {{- message[\'content\'] + eos_token }}\n'
|
||||
' {%- else %}\n'
|
||||
' {{- message[\'content\'][0][\'text\'] + eos_token }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {%- elif message["role"] == "tool_results" or message["role"] == "tool" %}\n'
|
||||
' {%- if message.content is defined and message.content.content is defined %}\n'
|
||||
' {%- set content = message.content.content %}\n'
|
||||
' {%- else %}\n'
|
||||
' {%- set content = message.content %}\n'
|
||||
' {%- endif %}\n'
|
||||
' {{- \'[TOOL_RESULTS] {"content": \' + content|string + ", " }}\n'
|
||||
' {%- if not message.tool_call_id is defined or message.tool_call_id|length < 9 %}\n'
|
||||
' {{- raise_exception("Tool call IDs should be alphanumeric strings with '
|
||||
'length >= 9! (2)" + message.tool_call_id) }}\n'
|
||||
' {%- endif %}\n'
|
||||
' {{- \'"call_id": "\' + message.tool_call_id[-9:] + \'"}[/TOOL_RESULTS]\' }}\n'
|
||||
' {%- else %}\n'
|
||||
' {{- raise_exception("Only user and assistant roles are supported, with the '
|
||||
'exception of an initial optional system message!") }}\n'
|
||||
' {%- endif %}\n'
|
||||
'{%- endfor %}')
|
||||
# manually set chat_template, as we're using mistral-3.1-24b-instruct-2503 tokenizer which
|
||||
# doesn't have the chat_template.json file
|
||||
engine.processor.chat_template = chat_template
|
||||
res2 = _infer_model(engine, system=devstral_small_2505_system)
|
||||
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_glm4_5():
|
||||
messages = [{'role': 'user', 'content': '浙江的省会在哪?'}]
|
||||
engine = TransformersEngine('ZhipuAI/GLM-4.5-Air')
|
||||
res = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, messages=messages)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_gpt_oss():
|
||||
messages = [{
|
||||
'role':
|
||||
'system',
|
||||
'content':
|
||||
'<|start|>system<|message|>You are Qwen.\nKnowledge cutoff: 2024-06\n'
|
||||
'Current date: 2025-08-08\n\nReasoning: medium\n\n'
|
||||
'# Valid channels: analysis, commentary, final. '
|
||||
'Channel must be included for every message.<|end|>'
|
||||
'<|start|>developer<|message|># Instructions\n\nYou are ChatGPT<|end|>'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'who are you?'
|
||||
}]
|
||||
engine = TransformersEngine('openai-mirror/gpt-oss-20b')
|
||||
res = _infer_model(engine, messages=messages)
|
||||
assert 'm Qwen' in res.rsplit('<|message|>', 1)[-1]
|
||||
|
||||
|
||||
def test_qwen3_next():
|
||||
engine = TransformersEngine('Qwen/Qwen3-Next-80B-A3B-Instruct')
|
||||
res = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_ernie_thinking():
|
||||
engine = TransformersEngine('PaddlePaddle/ERNIE-4.5-21B-A3B-Thinking')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_ring2():
|
||||
engine = TransformersEngine('inclusionAI/Ring-mini-2.0')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_ling2():
|
||||
engine = TransformersEngine('inclusionAI/Ling-mini-2.0')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_minimind():
|
||||
engine = TransformersEngine('gongjy/MiniMind2', model_type='minimind')
|
||||
swift_response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
jinja_response = _infer_model(engine)
|
||||
assert swift_response == jinja_response
|
||||
|
||||
|
||||
def test_medgemma3():
|
||||
engine = TransformersEngine('google/medgemma-27b-text-it')
|
||||
system = 'You are a helpful medical assistant.'
|
||||
messages = [{'role': 'user', 'content': 'How do you differentiate bacterial from viral pneumonia?'}]
|
||||
res = _infer_model(engine, system=system, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, system=system, messages=messages)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_youtu_llm():
|
||||
engine = TransformersEngine('Tencent-YouTu-Research/Youtu-LLM-2B')
|
||||
messages = [{'role': 'user', 'content': '你好'}]
|
||||
res = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
res2 = _infer_model(engine, messages=messages)
|
||||
assert res == res2, f'res: {res}, res2: {res2}'
|
||||
|
||||
|
||||
def test_glm4_moe_lite():
|
||||
engine = TransformersEngine('ZhipuAI/GLM-4.7-Flash')
|
||||
swift_response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
jinja_response = _infer_model(engine)
|
||||
assert swift_response == jinja_response
|
||||
|
||||
|
||||
def test_olmoe():
|
||||
engine = TransformersEngine('allenai/OLMoE-1B-7B-0924-Instruct')
|
||||
# engine = TransformersEngine('allenai/OLMoE-1B-7B-0125-Instruct')
|
||||
swift_response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
jinja_response = _infer_model(engine)
|
||||
assert swift_response == jinja_response
|
||||
|
||||
|
||||
def test_minicpm5():
|
||||
engine = TransformersEngine('OpenBMB/MiniCPM5-1B')
|
||||
swift_response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
jinja_response = _infer_model(engine)
|
||||
assert swift_response == jinja_response
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from swift.infer_engine import RequestConfig, TransformersEngine
|
||||
from swift.utils import get_logger, seed_everything
|
||||
logger = get_logger()
|
||||
# test_qwen2_5()
|
||||
# test_qwen1_5()
|
||||
# test_qwq()
|
||||
# test_internlm()
|
||||
# test_internlm2()
|
||||
# test_yi_coder()
|
||||
# test_yi()
|
||||
# test_deepseek_moe()
|
||||
# test_codegeex4()
|
||||
# test_chatglm4()
|
||||
# test_telechat()
|
||||
# test_telechat2()
|
||||
# test_glm_edge()
|
||||
# test_llama()
|
||||
# test_openbuddy()
|
||||
# test_megrez()
|
||||
# test_skywork_o1()
|
||||
# test_internlm2_reward()
|
||||
# test_qwen2_reward()
|
||||
# test_qwen2_5_math()
|
||||
# test_skywork_reward()
|
||||
# test_phi4()
|
||||
# test_phi4_mini()
|
||||
# test_internlm3()
|
||||
# test_deepseek_r1_distill()
|
||||
# test_deepseek_prover_v2()
|
||||
# test_qwen2_5_prm()
|
||||
# test_mistral_small()
|
||||
# test_baichuan_m1()
|
||||
# test_moonlight()
|
||||
# test_ling()
|
||||
# test_gemma3()
|
||||
# test_glm4()
|
||||
# test_qwen3()
|
||||
# test_qwen3_guard()
|
||||
# test_mimo()
|
||||
# test_minicpm()
|
||||
# test_minimax()
|
||||
# test_kimi_dev()
|
||||
# test_hunyuan()
|
||||
# test_ernie()
|
||||
# test_glm4_5()
|
||||
# test_devstral()
|
||||
# test_gpt_oss()
|
||||
# test_qwen3_next()
|
||||
# test_ernie_thinking()
|
||||
# test_ring2()
|
||||
# test_ling2()
|
||||
# test_minimind()
|
||||
# test_medgemma3()
|
||||
# test_youtu_llm()
|
||||
# test_glm4_moe_lite()
|
||||
# test_olmoe()
|
||||
test_minicpm5()
|
||||
@@ -0,0 +1,168 @@
|
||||
from swift.model import get_processor
|
||||
from swift.template import TemplateInputs, get_template
|
||||
|
||||
|
||||
def test_deepseek_v2_5():
|
||||
tokenizer = get_processor('deepseek-ai/DeepSeek-V2.5-1210')
|
||||
template = get_template(tokenizer)
|
||||
inputs = TemplateInputs({
|
||||
'messages': [{
|
||||
'role': 'system',
|
||||
'content': '000'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'aaa'
|
||||
}, {
|
||||
'role': 'assistant',
|
||||
'content': 'bbb'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'ccc'
|
||||
}]
|
||||
})
|
||||
res = template.encode(inputs)
|
||||
template.print_inputs(res)
|
||||
template.template_backend = 'jinja'
|
||||
res2 = template.encode(inputs)
|
||||
template.print_inputs(res2)
|
||||
assert res['input_ids'] == res2['input_ids']
|
||||
|
||||
|
||||
def test_qwen2_5_math_reward():
|
||||
tokenizer = get_processor('Qwen/Qwen2.5-Math-RM-72B')
|
||||
template = get_template(tokenizer)
|
||||
inputs = TemplateInputs({
|
||||
'messages': [{
|
||||
'role':
|
||||
'user',
|
||||
'content':
|
||||
'Janet’s ducks lay 16 eggs per day. She eats three for breakfast every morning and bakes muffins '
|
||||
"for her friends every day with four. She sells the remainder at the farmers' market daily for $2 per "
|
||||
"fresh duck egg. How much in dollars does she make every day at the farmers' market?"
|
||||
}, {
|
||||
'role':
|
||||
'assistant',
|
||||
'content':
|
||||
"To determine how much Janet makes from selling the duck eggs at the farmers' market, we need to "
|
||||
'follow these steps:\n\n1. Calculate the total number of eggs laid by the ducks each day.\n2. '
|
||||
'Determine how many eggs Janet eats and bakes for herself each day.\n3. Find out how many eggs are '
|
||||
"left to be sold.\n4. Calculate the revenue from selling the remaining eggs at $2 per egg.\n\nLet's "
|
||||
"start with the first step:\n\n1. Janet's ducks lay 16 eggs per day.\n\nNext, we calculate how many "
|
||||
'eggs Janet eats and bakes for herself each day:\n\n2. Janet eats 3 eggs for breakfast every morning.'
|
||||
'\n3. Janet bakes 4 eggs for her friends every day.\n\nSo, the total number of eggs Janet eats and '
|
||||
'bakes for herself each day is:\n\\[ 3 + 4 = 7 \\text{ eggs} \\]\n\nNow, we find out how many eggs '
|
||||
'are left to be sold:\n\\[ 16 - 7 = 9 \\text{ eggs} \\]\n\nFinally, we calculate the revenue from '
|
||||
'selling the remaining eggs at $2 per egg:\n\\[ 9 \\times 2 = 18 \\text{ dollars} \\]\n\nTherefore, '
|
||||
"Janet makes \\(\\boxed{18}\\) dollars every day at the farmers' market."
|
||||
}]
|
||||
})
|
||||
res = template.encode(inputs)
|
||||
template.print_inputs(res)
|
||||
template.template_backend = 'jinja'
|
||||
res2 = template.encode(inputs)
|
||||
template.print_inputs(res)
|
||||
assert res['input_ids'] == res2['input_ids']
|
||||
assert len(res['input_ids']) == 364
|
||||
|
||||
|
||||
def test_minimax():
|
||||
tokenizer = get_processor('MiniMax/MiniMax-Text-01')
|
||||
template = get_template(tokenizer)
|
||||
inputs = TemplateInputs({
|
||||
'messages': [{
|
||||
'role': 'system',
|
||||
'content': 'You are a helpful assistant created by MiniMax based on MiniMax-Text-01 model.'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'Hello!'
|
||||
}]
|
||||
})
|
||||
res = template.encode(inputs)
|
||||
template.print_inputs(res)
|
||||
assert tokenizer.decode(res['input_ids']) == (
|
||||
'<beginning_of_sentence>system ai_setting=assistant\nYou are a helpful assistant created by MiniMax based '
|
||||
'on MiniMax-Text-01 model.<end_of_sentence>\n<beginning_of_sentence>user name=user\nHello!<end_of_sentence>\n'
|
||||
'<beginning_of_sentence>ai name=assistant\n')
|
||||
|
||||
|
||||
def test_minimax_vl():
|
||||
tokenizer = get_processor('MiniMax/MiniMax-VL-01')
|
||||
template = get_template(tokenizer)
|
||||
inputs = TemplateInputs({
|
||||
'messages': [{
|
||||
'role': 'system',
|
||||
'content': 'You are a helpful assistant created by MiniMax based on MiniMax-VL-01 model.'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': '<image>Describe this image.'
|
||||
}],
|
||||
'images': ['http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png']
|
||||
})
|
||||
res = template.encode(inputs)
|
||||
assert len(res['input_ids']) == 5877
|
||||
|
||||
|
||||
def test_deepseek_v3_1():
|
||||
tokenizer = get_processor('deepseek-ai/DeepSeek-V3.1')
|
||||
template = get_template(tokenizer)
|
||||
inputs = {
|
||||
'messages': [{
|
||||
'role': 'system',
|
||||
'content': '000'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'aaa'
|
||||
}, {
|
||||
'role': 'assistant',
|
||||
'content': 'bbb'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'ccc'
|
||||
}]
|
||||
}
|
||||
res = template.encode(inputs)
|
||||
template.print_inputs(res)
|
||||
template.template_backend = 'jinja'
|
||||
res2 = template.encode(inputs)
|
||||
template.print_inputs(res2)
|
||||
assert res['input_ids'] == res2['input_ids']
|
||||
|
||||
|
||||
def test_preserve_thinking():
|
||||
tokenizer = get_processor('Qwen/Qwen3.6-35B-A3B')
|
||||
template = get_template(tokenizer, preserve_thinking=True)
|
||||
template.set_mode('train')
|
||||
inputs = {
|
||||
'messages': [{
|
||||
'role': 'system',
|
||||
'content': '000'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'aaa'
|
||||
}, {
|
||||
'role': 'assistant',
|
||||
'content': '<think>\nbbb\n</think>\n\nbbb'
|
||||
}, {
|
||||
'role': 'user',
|
||||
'content': 'ccc'
|
||||
}, {
|
||||
'role': 'assistant',
|
||||
'content': '<think>\nddd\n</think>\n\nddd'
|
||||
}]
|
||||
}
|
||||
template.template_backend = 'swift'
|
||||
res = template.encode(inputs)
|
||||
template.print_inputs(res)
|
||||
template.template_backend = 'jinja'
|
||||
res2 = template.encode(inputs)
|
||||
template.print_inputs(res2)
|
||||
assert res['input_ids'] == res2['input_ids']
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_deepseek_v2_5()
|
||||
# test_qwen2_5_math_reward()
|
||||
# test_minimax()
|
||||
# test_minimax_vl()
|
||||
# test_deepseek_v3_1()
|
||||
test_preserve_thinking()
|
||||
@@ -0,0 +1,74 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['SWIFT_DEBUG'] = '1'
|
||||
|
||||
tools = [{
|
||||
'name': 'get_current_weather',
|
||||
'description': 'Get the current weather in a given location',
|
||||
'parameters': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'location': {
|
||||
'type': 'string',
|
||||
'description': 'The city and state, e.g. San Francisco, CA'
|
||||
},
|
||||
'unit': {
|
||||
'type': 'string',
|
||||
'enum': ['celsius', 'fahrenheit']
|
||||
}
|
||||
},
|
||||
'required': ['location']
|
||||
}
|
||||
}]
|
||||
|
||||
|
||||
def _test_tool(engine, system=None):
|
||||
messages = [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': "How's the weather in Beijing today?"
|
||||
},
|
||||
{
|
||||
'role':
|
||||
'assistant',
|
||||
'content': ('<tool_call>\n{"name": "get_current_weather", "arguments": '
|
||||
'{"location": "Beijing, China", "unit": "celsius"}}\n</tool_call>')
|
||||
},
|
||||
{
|
||||
'role': 'tool',
|
||||
'content': "{'temp': 25, 'description': 'Partly cloudy', 'status': 'success'}"
|
||||
},
|
||||
]
|
||||
request_config = RequestConfig(max_tokens=512, temperature=0)
|
||||
response = engine.infer([InferRequest(messages=messages, tools=tools)], request_config=request_config)
|
||||
return response[0].choices[0].message.content
|
||||
|
||||
|
||||
def test_qwen2_5():
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-7B-Instruct')
|
||||
response = _test_tool(engine)
|
||||
assert response == 'Today in Beijing, the temperature is 25 degrees Celsius with partly cloudy skies.'
|
||||
|
||||
|
||||
def test_qwq():
|
||||
engine = TransformersEngine('Qwen/QwQ-32B')
|
||||
response = _test_tool(engine)
|
||||
assert response[-100:] == ('weather in Beijing is **25°C** with **partly cloudy** skies. '
|
||||
'It looks like a mild day outside—enjoy!')
|
||||
|
||||
|
||||
def test_deepseek_r1_distill():
|
||||
# TODO
|
||||
engine = TransformersEngine('deepseek-ai/DeepSeek-R1-Distill-Qwen-7B')
|
||||
_test_tool(engine, system='')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from swift.infer_engine import InferRequest, RequestConfig, TransformersEngine
|
||||
from swift.utils import get_logger
|
||||
logger = get_logger()
|
||||
# test_qwen2_5()
|
||||
test_qwq()
|
||||
# test_deepseek_r1_distill()
|
||||
@@ -0,0 +1,422 @@
|
||||
import os
|
||||
import torch
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1,2,3'
|
||||
os.environ['SWIFT_DEBUG'] = '1'
|
||||
|
||||
|
||||
def _infer_model(engine, system=None, messages=None, videos=None, max_tokens=128):
|
||||
seed_everything(42)
|
||||
request_config = RequestConfig(max_tokens=max_tokens, temperature=0)
|
||||
if messages is None:
|
||||
messages = []
|
||||
if not messages:
|
||||
if system is not None:
|
||||
messages += [{'role': 'system', 'content': system}]
|
||||
messages += [{'role': 'user', 'content': '你好'}]
|
||||
resp = engine.infer([{'messages': messages}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}, {'role': 'user', 'content': '<video>描述视频'}]
|
||||
else:
|
||||
messages = messages.copy()
|
||||
if videos is None:
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
resp = engine.infer([{'messages': messages, 'videos': videos}], request_config=request_config)
|
||||
response = resp[0].choices[0].message.content
|
||||
messages += [{'role': 'assistant', 'content': response}]
|
||||
logger.info(f'model: {engine.model_info.model_name}, messages: {messages}')
|
||||
return response
|
||||
|
||||
|
||||
def test_qwen2_vl():
|
||||
os.environ['FPS_MAX_FRAMES'] = '24'
|
||||
os.environ['MAX_PIXELS'] = '100352'
|
||||
os.environ['VIDEO_MAX_PIXELS'] = str(100352 // 4)
|
||||
engine = TransformersEngine('Qwen/Qwen2-VL-2B-Instruct')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_internvl2_5():
|
||||
engine = TransformersEngine('OpenGVLab/InternVL2_5-2B')
|
||||
_infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine, system='你是书生·万象,英文名是InternVL,是由上海人工智能实验室、清华大学及多家合作单位联合开发的多模态大语言模型。')
|
||||
|
||||
|
||||
def test_internvl2_5_mpo():
|
||||
engine = TransformersEngine('OpenGVLab/InternVL2_5-1B-MPO', model_type='internvl2_5')
|
||||
response = _infer_model(engine, messages=[{'role': 'user', 'content': '<video>这是什么'}])
|
||||
assert response == ('这是一段婴儿在阅读的视频。婴儿穿着浅绿色的上衣和粉色的裤子,戴着黑框眼镜,坐在床上,正在翻阅一本打开的书。'
|
||||
'背景中可以看到婴儿床、衣物和一些家具。视频中可以看到“clipo.com”的水印。婴儿看起来非常专注,似乎在认真地阅读。')
|
||||
|
||||
|
||||
def test_xcomposer2_5():
|
||||
engine = TransformersEngine('Shanghai_AI_Laboratory/internlm-xcomposer2d5-ol-7b:base', torch.float16)
|
||||
messages = [{'role': 'user', 'content': '<video>Describe the video'}]
|
||||
messages_with_system = messages.copy()
|
||||
messages_with_system.insert(0, {'role': 'system', 'content': ''})
|
||||
response = _infer_model(engine, messages=messages_with_system)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, system='')
|
||||
assert response == response2
|
||||
|
||||
response = _infer_model(engine, messages=messages)
|
||||
std_response = (
|
||||
'The video features a young child sitting on a bed, deeply engaged in reading a book. '
|
||||
'The child is dressed in a light blue sleeveless top and pink pants, and is wearing glasses. '
|
||||
'The bed is covered with a textured white blanket, and there are various items scattered on it, '
|
||||
'including a white cloth and a striped piece of clothing. In the background, '
|
||||
'a wooden crib and a dresser with a mirror can be seen. The child flips through the pages of the book, '
|
||||
'occasionally pausing to look at the illustrations. The child appears to be enjoying the book, '
|
||||
'and the overall atmosphere is one of quiet concentration and enjoyment.')
|
||||
|
||||
assert response == std_response[:len(response)]
|
||||
|
||||
|
||||
def test_mplug3():
|
||||
engine = TransformersEngine('iic/mPLUG-Owl3-7B-240728')
|
||||
# engine = TransformersEngine('iic/mPLUG-Owl3-7B-241101')
|
||||
_infer_model(engine, system='')
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine, system='')
|
||||
|
||||
|
||||
def test_minicpmv():
|
||||
engine = TransformersEngine('OpenBMB/MiniCPM-V-2_6')
|
||||
_infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def test_minicpmo():
|
||||
os.environ['VIDEO_MAX_SLICE_NUMS'] = '2'
|
||||
engine = TransformersEngine('OpenBMB/MiniCPM-o-2_6')
|
||||
messages = [{'role': 'user', 'content': '<video>Describe the video'}]
|
||||
response = _infer_model(engine, messages=messages)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages)
|
||||
assert response == response2 == (
|
||||
'The video features a young child sitting on a bed, deeply engrossed in reading a large book. The child, '
|
||||
'dressed in a light blue sleeveless top and pink pants, is surrounded by a cozy and homely environment. '
|
||||
'The bed is adorned with a patterned blanket, and a white cloth is casually draped over the side. '
|
||||
'In the background, a crib and a television are visible, adding to the domestic setting. '
|
||||
'The child is seen flipping through the pages of the book, occasionally pausing to look at the pages, '
|
||||
'and then continuing to turn them. The video captures the child\'s focused and curious demeanor as they '
|
||||
'explore the contents of the book, creating a heartwarming '
|
||||
'scene of a young reader immersed in their world of stories.')[:len(response)]
|
||||
|
||||
|
||||
def test_valley():
|
||||
engine = TransformersEngine('bytedance-research/Valley-Eagle-7B')
|
||||
_infer_model(engine)
|
||||
|
||||
|
||||
def _run_qwen2_5_vl_hf(messages, model, template):
|
||||
from qwen_vl_utils import process_vision_info
|
||||
processor = template.processor
|
||||
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
||||
images, videos, video_kwargs = process_vision_info(messages, return_video_kwargs=True)
|
||||
inputs = processor(text=text, images=images, videos=videos, do_resize=False, return_tensors='pt', **video_kwargs)
|
||||
inputs = inputs.to(model.device)
|
||||
|
||||
generated_ids = model.generate(**inputs, max_new_tokens=128, do_sample=False)
|
||||
generated_ids_trimmed = [out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)]
|
||||
output_text = processor.batch_decode(
|
||||
generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False)
|
||||
return output_text[0]
|
||||
|
||||
|
||||
def test_qwen2_5_vl():
|
||||
os.environ['FPS'] = '1'
|
||||
os.environ['VIDEO_MAX_PIXELS'] = str(360 * 420)
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-VL-7B-Instruct')
|
||||
query = 'What happened in the video?'
|
||||
messages = [{'role': 'user', 'content': query}]
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, videos=videos)
|
||||
messages = [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': [
|
||||
{
|
||||
'type': 'video',
|
||||
'video': videos[0]
|
||||
},
|
||||
{
|
||||
'type': 'text',
|
||||
'text': query
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
response3 = _run_qwen2_5_vl_hf(messages, engine.model, engine.template)
|
||||
assert response == response2 == response3
|
||||
|
||||
|
||||
def test_qwen2_5_omni():
|
||||
USE_AUDIO_IN_VIDEO = True
|
||||
os.environ['USE_AUDIO_IN_VIDEO'] = str(USE_AUDIO_IN_VIDEO)
|
||||
engine = TransformersEngine('Qwen/Qwen2.5-Omni-7B', attn_impl='flash_attn')
|
||||
system = ('You are Qwen, a virtual human developed by the Qwen Team, Alibaba Group, '
|
||||
'capable of perceiving auditory and visual inputs, as well as generating text and speech.')
|
||||
messages = [{'role': 'system', 'content': system}, {'role': 'user', 'content': '<video>'}]
|
||||
videos = ['https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2.5-Omni/draw.mp4']
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, videos=videos)
|
||||
if USE_AUDIO_IN_VIDEO:
|
||||
ground_truth = ("Oh, that's a really cool drawing! It looks like a guitar. You've got the body "
|
||||
'and the neck drawn in a simple yet effective way. The lines are clean and the '
|
||||
'shape is well-defined. What made you choose to draw a guitar?')
|
||||
else:
|
||||
ground_truth = ('嗯,你是在用平板画画呢。你画的这把吉他,看起来很简洁明了。你用的笔触也很流畅,线条很清晰。你对颜色的运用也很不错,整体看起来很协调。你要是还有啥想法或者问题,随时跟我说哈。')
|
||||
assert response == response2 == ground_truth
|
||||
|
||||
|
||||
def _run_qwen3_omni_hf(model, processor, messages):
|
||||
from qwen_omni_utils import process_mm_info
|
||||
text = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
|
||||
audios, images, videos = process_mm_info(messages, use_audio_in_video=True)
|
||||
inputs = processor(
|
||||
text=text,
|
||||
audio=audios,
|
||||
images=images,
|
||||
videos=videos,
|
||||
return_tensors='pt',
|
||||
padding=True,
|
||||
use_audio_in_video=True)
|
||||
inputs = inputs.to(device=model.device, dtype=model.dtype)
|
||||
text_ids = model.generate(**inputs, use_audio_in_video=True, do_sample=False, max_new_tokens=128)
|
||||
text = processor.decode(
|
||||
text_ids[0][len(inputs['input_ids'][0]):], skip_special_tokens=True, clean_up_tokenization_spaces=False)
|
||||
return text
|
||||
|
||||
|
||||
def test_qwen3_omni():
|
||||
USE_AUDIO_IN_VIDEO = True
|
||||
os.environ['USE_AUDIO_IN_VIDEO'] = str(USE_AUDIO_IN_VIDEO)
|
||||
engine = TransformersEngine('Qwen/Qwen3-Omni-30B-A3B-Thinking')
|
||||
query = 'describe the video.'
|
||||
messages = [{'role': 'user', 'content': query}]
|
||||
videos = ['https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen2.5-Omni/draw.mp4']
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
|
||||
messages = [
|
||||
{
|
||||
'role': 'user',
|
||||
'content': [
|
||||
{
|
||||
'type': 'video',
|
||||
'video': videos[0]
|
||||
},
|
||||
{
|
||||
'type': 'text',
|
||||
'text': query
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
response2 = _run_qwen3_omni_hf(engine.model, engine.processor, messages)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_glm4_1v():
|
||||
messages = [{'role': 'user', 'content': '<video>What happened in the video?'}]
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
engine = TransformersEngine('ZhipuAI/GLM-4.1V-9B-Thinking')
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, videos=videos)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_glm4_5v():
|
||||
messages = [{'role': 'user', 'content': '<video>What happened in the video?'}]
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
engine = TransformersEngine('ZhipuAI/GLM-4.5V')
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, videos=videos)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_keye_vl():
|
||||
engine = TransformersEngine('Kwai-Keye/Keye-VL-8B-Preview')
|
||||
messages = [{'role': 'user', 'content': '<video>Describe this video.'}]
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, videos=videos)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_keye_vl_1_5():
|
||||
engine = TransformersEngine('Kwai-Keye/Keye-VL-1_5-8B')
|
||||
messages = [{'role': 'user', 'content': '<video>Describe this video.'}]
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
assert response[:200] == ('The video features a young child sitting on a bed, engrossed in '
|
||||
'reading a book. The child is wearing a light blue sleeveless top and pink '
|
||||
'pants. The book appears to be a hardcover with illustrations, ')
|
||||
|
||||
|
||||
def test_ovis2_5():
|
||||
engine = TransformersEngine('AIDC-AI/Ovis2.5-2B')
|
||||
messages = [{'role': 'user', 'content': '<video>Describe this video in detail.'}]
|
||||
videos = ['baby.mp4']
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
print(f'response: {response}')
|
||||
|
||||
|
||||
def run_hf(model, processor, messages):
|
||||
inputs = processor.apply_chat_template(
|
||||
messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors='pt').to(
|
||||
model.device, dtype=torch.bfloat16)
|
||||
generate_ids = model.generate(**inputs, max_new_tokens=128, do_sample=False)
|
||||
decoded_output = processor.decode(generate_ids[0, inputs['input_ids'].shape[1]:], skip_special_tokens=True)
|
||||
return decoded_output
|
||||
|
||||
|
||||
def test_interns1():
|
||||
engine = TransformersEngine('Shanghai_AI_Laboratory/Intern-S1-mini')
|
||||
query = 'Describe this video in detail.'
|
||||
messages = [{'role': 'user', 'content': f'<video>{query}'}]
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, videos=videos)
|
||||
messages = [{
|
||||
'role': 'user',
|
||||
'content': [
|
||||
{
|
||||
'type': 'video',
|
||||
'url': videos[0]
|
||||
},
|
||||
{
|
||||
'type': 'text',
|
||||
'text': query
|
||||
},
|
||||
],
|
||||
}]
|
||||
response2 = run_hf(engine.model, engine.processor, messages)
|
||||
assert response == ('<think>' + response2)[:len(response)]
|
||||
|
||||
|
||||
def test_internvl3_5():
|
||||
models = [
|
||||
'OpenGVLab/InternVL3_5-1B', 'OpenGVLab/InternVL3_5-2B', 'OpenGVLab/InternVL3_5-4B', 'OpenGVLab/InternVL3_5-8B',
|
||||
'OpenGVLab/InternVL3_5-14B', 'OpenGVLab/InternVL3_5-38B', 'OpenGVLab/InternVL3_5-30B-A3B',
|
||||
'OpenGVLab/InternVL3_5-GPT-OSS-20B-A4B-Preview'
|
||||
]
|
||||
for model in models:
|
||||
engine = TransformersEngine(model)
|
||||
messages = [{'role': 'user', 'content': '<video>Describe this video in detail.'}]
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, videos=videos)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def test_minicpmv4_5():
|
||||
engine = TransformersEngine('OpenBMB/MiniCPM-V-4_5')
|
||||
messages = [{'role': 'user', 'content': '<video>Describe this video in detail.'}]
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, videos=videos)
|
||||
assert response == response2
|
||||
|
||||
|
||||
def _run_qwen3_vl_hf(messages, model, template):
|
||||
from qwen_vl_utils import process_vision_info
|
||||
processor = template.processor
|
||||
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
||||
images, videos, video_kwargs = process_vision_info(
|
||||
messages, image_patch_size=16, return_video_kwargs=True, return_video_metadata=True)
|
||||
if videos is not None:
|
||||
videos, video_metadatas = zip(*videos)
|
||||
videos, video_metadatas = list(videos), list(video_metadatas)
|
||||
else:
|
||||
video_metadatas = None
|
||||
inputs = processor(
|
||||
text=text,
|
||||
images=images,
|
||||
videos=videos,
|
||||
video_metadata=video_metadatas,
|
||||
do_resize=False,
|
||||
return_tensors='pt',
|
||||
**video_kwargs)
|
||||
inputs = inputs.to(model.device)
|
||||
|
||||
generated_ids = model.generate(**inputs, max_new_tokens=128, do_sample=False)
|
||||
generated_ids_trimmed = [out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)]
|
||||
output_text = processor.batch_decode(
|
||||
generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False)
|
||||
return output_text[0]
|
||||
|
||||
|
||||
def test_qwen3_vl():
|
||||
engine = TransformersEngine('Qwen/Qwen3-VL-4B-Instruct')
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
query = 'describe this video.'
|
||||
messages = [{'role': 'user', 'content': query}]
|
||||
response = _infer_model(engine, messages=messages, videos=videos)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine, messages=messages, videos=videos)
|
||||
messages = [{
|
||||
'role': 'user',
|
||||
'content': [
|
||||
{
|
||||
'type': 'video',
|
||||
'video': videos[0],
|
||||
},
|
||||
{
|
||||
'type': 'text',
|
||||
'text': query
|
||||
},
|
||||
],
|
||||
}]
|
||||
response3 = _run_qwen3_vl_hf(messages, engine.model, engine.template)
|
||||
assert response == response2 == response3
|
||||
|
||||
|
||||
def test_qwen3_vl_moe():
|
||||
engine = TransformersEngine('Qwen/Qwen3-VL-30B-A3B-Instruct')
|
||||
response = _infer_model(engine)
|
||||
engine.template.template_backend = 'jinja'
|
||||
response2 = _infer_model(engine)
|
||||
assert response == response2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from swift.infer_engine import RequestConfig, TransformersEngine
|
||||
from swift.utils import get_logger, seed_everything
|
||||
logger = get_logger()
|
||||
# test_qwen2_vl()
|
||||
# test_internvl2_5()
|
||||
# test_xcomposer2_5()
|
||||
# test_internvl2_5_mpo()
|
||||
# test_mplug3()
|
||||
# test_minicpmv()
|
||||
# test_minicpmo()
|
||||
# test_valley()
|
||||
# test_qwen2_5_vl()
|
||||
# test_qwen2_5_omni()
|
||||
# test_qwen3_omni()
|
||||
# test_glm4_1v() # bug now, wait model fix
|
||||
# test_keye_vl()
|
||||
# test_keye_vl_1_5()
|
||||
# test_glm4_5v()
|
||||
# test_ovis2_5()
|
||||
# test_interns1()
|
||||
# test_internvl3_5()
|
||||
# test_minicpmv4_5()
|
||||
test_qwen3_vl()
|
||||
# test_qwen3_vl_moe()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,208 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def _infer_audio(model, use_chat_template: bool = True, max_model_len=8192, system=None, limit_mm_per_prompt=None):
|
||||
limit_mm_per_prompt = limit_mm_per_prompt or {'audio': 2}
|
||||
engine = VllmEngine(
|
||||
model,
|
||||
max_model_len=max_model_len,
|
||||
gpu_memory_utilization=0.6,
|
||||
limit_mm_per_prompt=limit_mm_per_prompt,
|
||||
enforce_eager=True)
|
||||
if not use_chat_template:
|
||||
engine.template.use_chat_template = False
|
||||
audios = ['http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/weather.wav']
|
||||
messages = []
|
||||
if system is not None:
|
||||
messages += [{'role': 'system', 'content': system}]
|
||||
messages.append({'role': 'user', 'content': 'describe the audio.'})
|
||||
resp_list = engine.infer([InferRequest(messages=messages, audios=audios)],
|
||||
RequestConfig(temperature=0, max_tokens=64, repetition_penalty=1.))
|
||||
return resp_list[0].choices[0].message.content
|
||||
|
||||
|
||||
def _infer_image(model, use_chat_template: bool = True, max_model_len=8192, system=None, limit_mm_per_prompt=None):
|
||||
limit_mm_per_prompt = limit_mm_per_prompt or {'image': 5, 'video': 0}
|
||||
engine = VllmEngine(
|
||||
model,
|
||||
max_model_len=max_model_len,
|
||||
gpu_memory_utilization=0.6,
|
||||
limit_mm_per_prompt=limit_mm_per_prompt,
|
||||
enforce_eager=True)
|
||||
if not use_chat_template:
|
||||
engine.template.use_chat_template = False
|
||||
images = ['http://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/cat.png']
|
||||
messages = []
|
||||
if system is not None:
|
||||
messages += [{'role': 'system', 'content': system}]
|
||||
messages.append({'role': 'user', 'content': 'describe the image.'})
|
||||
resp_list = engine.infer([InferRequest(messages=messages, images=images)],
|
||||
RequestConfig(temperature=0, max_tokens=64, repetition_penalty=1.))
|
||||
return resp_list[0].choices[0].message.content
|
||||
|
||||
|
||||
def _infer_video(model, use_chat_template: bool = True, max_model_len=8192, system=None, limit_mm_per_prompt=None):
|
||||
limit_mm_per_prompt = limit_mm_per_prompt or {'image': 16, 'video': 2}
|
||||
engine = VllmEngine(
|
||||
model,
|
||||
max_model_len=max_model_len,
|
||||
limit_mm_per_prompt=limit_mm_per_prompt,
|
||||
gpu_memory_utilization=0.6,
|
||||
enforce_eager=True)
|
||||
if not use_chat_template:
|
||||
engine.template.use_chat_template = False
|
||||
videos = ['https://modelscope-open.oss-cn-hangzhou.aliyuncs.com/images/baby.mp4']
|
||||
messages = []
|
||||
if system is not None:
|
||||
messages += [{'role': 'system', 'content': system}]
|
||||
messages.append({'role': 'user', 'content': 'describe the video.'})
|
||||
resp_list = engine.infer([InferRequest(messages=messages, videos=videos)],
|
||||
RequestConfig(temperature=0, max_tokens=64, repetition_penalty=1.))
|
||||
return resp_list[0].choices[0].message.content
|
||||
|
||||
|
||||
def test_qwen2_audio():
|
||||
response = _infer_audio('Qwen/Qwen2-Audio-7B-Instruct')
|
||||
assert response == "The audio is a man speaking in Mandarin saying '今天天气真好呀'."
|
||||
|
||||
|
||||
def test_qwen2_vl():
|
||||
response = _infer_image('Qwen/Qwen2-VL-2B-Instruct')
|
||||
assert response == (
|
||||
'The image depicts a cute kitten with a fluffy, white and gray striped coat. The kitten has large, '
|
||||
'expressive blue eyes and is looking directly at the camera. Its ears are perked up, and it has a '
|
||||
'small red mark on its left ear. The background is blurred, focusing attention on the kitten. The overall')
|
||||
|
||||
|
||||
def test_qwen2_5_vl():
|
||||
response = _infer_image('Qwen/Qwen2.5-VL-3B-Instruct')
|
||||
assert response == (
|
||||
'The image depicts a cute, fluffy kitten with striking blue eyes and a white and gray fur pattern. '
|
||||
'The kitten has a small, pink nose and is looking directly at the camera with a curious expression. '
|
||||
"The background is blurred, drawing attention to the kitten's face. "
|
||||
'The overall appearance is very endearing and charming.')
|
||||
|
||||
|
||||
def test_deepseek_vl_v2():
|
||||
response = _infer_image('deepseek-ai/deepseek-vl2-tiny', max_model_len=4096)
|
||||
assert response == ('The image depicts a close-up of a adorable kitten with large, expressive eyes. The kitten has '
|
||||
'a mix of white and gray fur with distinct black stripes, giving it a tabby-like appearance. '
|
||||
'Its ears are perked up, and its whiskers are prominently visible. The background is blurred, '
|
||||
'focusing attention on the kitten')
|
||||
|
||||
|
||||
def test_internvl2():
|
||||
response = _infer_image('OpenGVLab/InternVL2-2B', max_model_len=4096, system='')
|
||||
assert response == ('The image features a kitten with striking blue eyes and a mix of white and black fur. '
|
||||
'The kitten has large, expressive eyes and a small, pink nose. Its ears are perked up, '
|
||||
'and it appears to be looking directly at the camera. The fur is soft and fluffy, with a mix')
|
||||
|
||||
|
||||
def test_minicpmv_2_5():
|
||||
response = _infer_image('OpenBMB/MiniCPM-Llama3-V-2_5', max_model_len=4096)
|
||||
assert response == (
|
||||
"The image is a digital painting of a kitten that captures the essence of a young feline's innocence "
|
||||
"and curiosity. The kitten's fur is rendered with a mix of gray, white, and black stripes, "
|
||||
'giving it a realistic and adorable appearance. Its large, expressive eyes are a striking blue, '
|
||||
"which draws the viewer's")
|
||||
|
||||
|
||||
def test_minicpmv_2_6():
|
||||
response = _infer_image('OpenBMB/MiniCPM-V-2_6', max_model_len=4096)
|
||||
assert response == (
|
||||
'The image features a close-up of a kitten with striking blue eyes and a mix of '
|
||||
"white and dark fur, possibly gray or black. The kitten's gaze is directed forward, giving it an "
|
||||
"expressive and captivating look. The background is blurred, drawing focus to the kitten's face. "
|
||||
"The overall composition emphasizes the kitten's features")
|
||||
|
||||
|
||||
def test_minicpmo_2_6_video():
|
||||
response = _infer_video('OpenBMB/MiniCPM-o-2_6')
|
||||
assert response == ('The video features a young child sitting on a bed, deeply engaged in reading a book. '
|
||||
'The child, dressed in a light blue sleeveless top and pink pants, is surrounded by a '
|
||||
'cozy and homely environment. The bed is adorned with a patterned blanket, and a white cloth '
|
||||
'is casually draped over the side.')
|
||||
|
||||
|
||||
def test_qwen2_5_vl_video():
|
||||
response = _infer_video('Qwen/Qwen2.5-VL-3B-Instruct')
|
||||
assert response == ('A baby wearing sunglasses is sitting on a bed and reading a book. '
|
||||
'The baby is holding the book with both hands and is looking at the pages. '
|
||||
'The baby is wearing a light blue shirt and pink pants. The baby is sitting '
|
||||
'on a white blanket. The baby is looking at the book and is smiling. The baby')
|
||||
|
||||
|
||||
def test_qwen2_5_omni():
|
||||
limit_mm_per_prompt = {'image': 1, 'video': 1, 'audio': 1}
|
||||
response = _infer_video('Qwen/Qwen2.5-Omni-7B', limit_mm_per_prompt=limit_mm_per_prompt)
|
||||
# response = _infer_audio('Qwen/Qwen2.5-Omni-7B')
|
||||
assert response
|
||||
|
||||
|
||||
def test_ovis2():
|
||||
response = _infer_image('AIDC-AI/Ovis2-1B', max_model_len=4096)
|
||||
assert response[:200] == ('The image showcases a charming digital painting of a kitten, capturing its '
|
||||
'adorable features in a unique style. The kitten has a predominantly white face '
|
||||
'with black stripes and spots, giving it a stri')
|
||||
|
||||
|
||||
def test_keye_vl():
|
||||
response = _infer_image('Kwai-Keye/Keye-VL-8B-Preview', max_model_len=4096)
|
||||
assert response[:200] == ('<analysis>This question asks for a description of the image, which is '
|
||||
'straightforward and involves observing the visual content. Therefore, '
|
||||
'/no_think is more appropriate.</analysis>The image features ')
|
||||
|
||||
|
||||
def test_kimi_vl():
|
||||
response = _infer_image('moonshotai/Kimi-VL-A3B-Instruct', max_model_len=4096)
|
||||
print(f'response: {response}')
|
||||
|
||||
|
||||
def test_glm4v():
|
||||
response = _infer_image('ZhipuAI/glm-4v-9b', max_model_len=4096)
|
||||
print(f'response: {response}')
|
||||
|
||||
|
||||
def test_glm4_1v():
|
||||
response = _infer_image('ZhipuAI/GLM-4.1V-9B-Thinking', max_model_len=4096)
|
||||
print(f'response: {response}')
|
||||
|
||||
|
||||
def test_paddleocr_vl():
|
||||
response = _infer_image('PaddlePaddle/PaddleOCR-VL', max_model_len=4096)
|
||||
print(f'response: {response}')
|
||||
|
||||
|
||||
def test_glm4_5_vl():
|
||||
response = _infer_image('ZhipuAI/GLM-4.5V', max_model_len=4096)
|
||||
print(f'response: {response}')
|
||||
|
||||
|
||||
def test_deepseek_ocr():
|
||||
response = _infer_image('deepseek-ai/DeepSeek-OCR', max_model_len=4096)
|
||||
print(f'response: {response}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from swift.infer_engine import InferRequest, RequestConfig, VllmEngine
|
||||
|
||||
# test_qwen2_vl()
|
||||
# test_qwen2_5_vl()
|
||||
# test_deepseek_vl_v2()
|
||||
# test_internvl2()
|
||||
# test_qwen2_audio()
|
||||
# test_minicpmv_2_5()
|
||||
# test_minicpmv_2_6()
|
||||
# test_minicpmo_2_6_video()
|
||||
# test_qwen2_5_vl_video()
|
||||
# test_qwen2_5_omni()
|
||||
# test_ovis2()
|
||||
# test_keye_vl()
|
||||
# test_kimi_vl()
|
||||
# test_glm4v()
|
||||
# test_glm4_1v()
|
||||
# test_paddleocr_vl()
|
||||
test_deepseek_ocr()
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
|
||||
import copy
|
||||
import numpy as np
|
||||
import os
|
||||
import pickle
|
||||
import requests
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import unittest
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Mapping
|
||||
from modelscope.hub.constants import DEFAULT_CREDENTIALS_PATH
|
||||
from os.path import expanduser
|
||||
|
||||
TEST_LEVEL = 2
|
||||
TEST_LEVEL_STR = 'TEST_LEVEL'
|
||||
|
||||
# for user citest and sdkdev
|
||||
TEST_ACCESS_TOKEN1 = os.environ.get('TEST_ACCESS_TOKEN_CITEST', None)
|
||||
TEST_ACCESS_TOKEN2 = os.environ.get('TEST_ACCESS_TOKEN_SDKDEV', None)
|
||||
|
||||
TEST_MODEL_CHINESE_NAME = '内部测试模型'
|
||||
TEST_MODEL_ORG = 'citest'
|
||||
|
||||
|
||||
def delete_credential():
|
||||
path_credential = expanduser(DEFAULT_CREDENTIALS_PATH)
|
||||
shutil.rmtree(path_credential, ignore_errors=True)
|
||||
|
||||
|
||||
def test_level():
|
||||
global TEST_LEVEL
|
||||
if TEST_LEVEL_STR in os.environ:
|
||||
TEST_LEVEL = int(os.environ[TEST_LEVEL_STR])
|
||||
|
||||
return TEST_LEVEL
|
||||
|
||||
|
||||
def require_tf(test_case):
|
||||
test_case = unittest.skip('test requires TensorFlow')(test_case)
|
||||
return test_case
|
||||
|
||||
|
||||
def require_torch(test_case):
|
||||
return test_case
|
||||
|
||||
|
||||
def set_test_level(level: int):
|
||||
global TEST_LEVEL
|
||||
TEST_LEVEL = level
|
||||
|
||||
|
||||
class DummyTorchDataset:
|
||||
|
||||
def __init__(self, feat, label, num) -> None:
|
||||
self.feat = feat
|
||||
self.label = label
|
||||
self.num = num
|
||||
|
||||
def __getitem__(self, index):
|
||||
import torch
|
||||
return {'feat': torch.Tensor(self.feat), 'labels': torch.Tensor(self.label)}
|
||||
|
||||
def __len__(self):
|
||||
return self.num
|
||||
|
||||
|
||||
def create_dummy_test_dataset(feat, label, num):
|
||||
return DummyTorchDataset(feat, label, num)
|
||||
|
||||
|
||||
def download_and_untar(fpath, furl, dst) -> str:
|
||||
if not os.path.exists(fpath):
|
||||
r = requests.get(furl)
|
||||
with open(fpath, 'wb') as f:
|
||||
f.write(r.content)
|
||||
|
||||
file_name = os.path.basename(fpath)
|
||||
root_dir = os.path.dirname(fpath)
|
||||
target_dir_name = os.path.splitext(os.path.splitext(file_name)[0])[0]
|
||||
target_dir_path = os.path.join(root_dir, target_dir_name)
|
||||
|
||||
# untar the file
|
||||
t = tarfile.open(fpath)
|
||||
t.extractall(path=dst)
|
||||
|
||||
return target_dir_path
|
||||
|
||||
|
||||
def get_case_model_info():
|
||||
status_code, result = subprocess.getstatusoutput(
|
||||
'grep -rn "damo/" tests/ | grep -v ".pyc" | grep -v "Binary file" | grep -v run.py ')
|
||||
lines = result.split('\n')
|
||||
test_cases = OrderedDict()
|
||||
model_cases = OrderedDict()
|
||||
for line in lines:
|
||||
# "tests/msdatasets/test_ms_dataset.py:92: model_id = 'damo/bert-base-sst2'"
|
||||
line = line.strip()
|
||||
elements = line.split(':')
|
||||
test_file = elements[0]
|
||||
model_pos = line.find('damo')
|
||||
left_quote = line[model_pos - 1]
|
||||
rquote_idx = line.rfind(left_quote)
|
||||
model_name = line[model_pos:rquote_idx]
|
||||
if test_file not in test_cases:
|
||||
test_cases[test_file] = set()
|
||||
model_info = test_cases[test_file]
|
||||
model_info.add(model_name)
|
||||
|
||||
if model_name not in model_cases:
|
||||
model_cases[model_name] = set()
|
||||
case_info = model_cases[model_name]
|
||||
case_info.add(test_file.replace('tests/', '').replace('.py', '').replace('/', '.'))
|
||||
|
||||
return model_cases
|
||||
|
||||
|
||||
def compare_arguments_nested(print_content, arg1, arg2, rtol=1.e-3, atol=1.e-8, ignore_unknown_type=True):
|
||||
type1 = type(arg1)
|
||||
type2 = type(arg2)
|
||||
if type1.__name__ != type2.__name__:
|
||||
if print_content is not None:
|
||||
print(f'{print_content}, type not equal:{type1.__name__} and {type2.__name__}')
|
||||
return False
|
||||
|
||||
if arg1 is None:
|
||||
return True
|
||||
elif isinstance(arg1, (int, str, bool, np.bool_, np.integer, np.str_)):
|
||||
if arg1 != arg2:
|
||||
if print_content is not None:
|
||||
print(f'{print_content}, arg1:{arg1}, arg2:{arg2}')
|
||||
return False
|
||||
return True
|
||||
elif isinstance(arg1, (float, np.floating)):
|
||||
if not np.isclose(arg1, arg2, rtol=rtol, atol=atol, equal_nan=True):
|
||||
if print_content is not None:
|
||||
print(f'{print_content}, arg1:{arg1}, arg2:{arg2}')
|
||||
return False
|
||||
return True
|
||||
elif isinstance(arg1, (tuple, list)):
|
||||
if len(arg1) != len(arg2):
|
||||
if print_content is not None:
|
||||
print(f'{print_content}, length is not equal:{len(arg1)}, {len(arg2)}')
|
||||
return False
|
||||
if not all([
|
||||
compare_arguments_nested(None, sub_arg1, sub_arg2, rtol=rtol, atol=atol)
|
||||
for sub_arg1, sub_arg2 in zip(arg1, arg2)
|
||||
]):
|
||||
if print_content is not None:
|
||||
print(f'{print_content}')
|
||||
return False
|
||||
return True
|
||||
elif isinstance(arg1, Mapping):
|
||||
keys1 = arg1.keys()
|
||||
keys2 = arg2.keys()
|
||||
if len(keys1) != len(keys2):
|
||||
if print_content is not None:
|
||||
print(f'{print_content}, key length is not equal:{len(keys1)}, {len(keys2)}')
|
||||
return False
|
||||
if len(set(keys1) - set(keys2)) > 0:
|
||||
if print_content is not None:
|
||||
print(f'{print_content}, key diff:{set(keys1) - set(keys2)}')
|
||||
return False
|
||||
if not all([compare_arguments_nested(None, arg1[key], arg2[key], rtol=rtol, atol=atol) for key in keys1]):
|
||||
if print_content is not None:
|
||||
print(f'{print_content}')
|
||||
return False
|
||||
return True
|
||||
elif isinstance(arg1, np.ndarray):
|
||||
arg1 = np.where(np.equal(arg1, None), np.NaN, arg1).astype(dtype=float)
|
||||
arg2 = np.where(np.equal(arg2, None), np.NaN, arg2).astype(dtype=float)
|
||||
if not all(np.isclose(arg1, arg2, rtol=rtol, atol=atol, equal_nan=True).flatten()):
|
||||
if print_content is not None:
|
||||
print(f'{print_content}')
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
if ignore_unknown_type:
|
||||
return True
|
||||
else:
|
||||
raise ValueError(f'type not supported: {type1}')
|
||||
|
||||
|
||||
_DIST_SCRIPT_TEMPLATE = """
|
||||
import ast
|
||||
import argparse
|
||||
import pickle
|
||||
import torch
|
||||
from torch import distributed as dist
|
||||
from modelscope.utils.torch_utils import get_dist_info
|
||||
import {}
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--save_all_ranks', type=ast.literal_eval, help='save all ranks results')
|
||||
parser.add_argument('--save_file', type=str, help='save file')
|
||||
parser.add_argument('--local_rank', type=int, default=0)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
results = {}.{}({}) # module.func(params)
|
||||
if args.save_all_ranks:
|
||||
save_file = args.save_file + str(dist.get_rank())
|
||||
with open(save_file, 'wb') as f:
|
||||
pickle.dump(results, f)
|
||||
else:
|
||||
rank, _ = get_dist_info()
|
||||
if rank == 0:
|
||||
with open(args.save_file, 'wb') as f:
|
||||
pickle.dump(results, f)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
"""
|
||||
|
||||
|
||||
class DistributedTestCase(unittest.TestCase):
|
||||
"""Distributed TestCase for test function with distributed mode.
|
||||
Examples:
|
||||
>>> import torch
|
||||
>>> from torch import distributed as dist
|
||||
>>> from modelscope.utils.torch_utils import init_dist
|
||||
|
||||
>>> def _test_func(*args, **kwargs):
|
||||
>>> init_dist(launcher='pytorch')
|
||||
>>> rank = dist.get_rank()
|
||||
>>> if rank == 0:
|
||||
>>> value = torch.tensor(1.0).cuda()
|
||||
>>> else:
|
||||
>>> value = torch.tensor(2.0).cuda()
|
||||
>>> dist.all_reduce(value)
|
||||
>>> return value.cpu().numpy()
|
||||
|
||||
>>> class DistTest(DistributedTestCase):
|
||||
>>> def test_function_dist(self):
|
||||
>>> args = () # args should be python builtin type
|
||||
>>> kwargs = {} # kwargs should be python builtin type
|
||||
>>> self.start(
|
||||
>>> _test_func,
|
||||
>>> num_gpus=2,
|
||||
>>> assert_callback=lambda x: self.assertEqual(x, 3.0),
|
||||
>>> *args,
|
||||
>>> **kwargs,
|
||||
>>> )
|
||||
"""
|
||||
|
||||
def _start(self, dist_start_cmd, func, num_gpus, assert_callback=None, save_all_ranks=False, *args, **kwargs):
|
||||
script_path = func.__code__.co_filename
|
||||
script_dir, script_name = os.path.split(script_path)
|
||||
script_name = os.path.splitext(script_name)[0]
|
||||
func_name = func.__qualname__
|
||||
|
||||
func_params = []
|
||||
for arg in args:
|
||||
if isinstance(arg, str):
|
||||
arg = ('\'{}\''.format(arg))
|
||||
func_params.append(str(arg))
|
||||
|
||||
for k, v in kwargs.items():
|
||||
if isinstance(v, str):
|
||||
v = ('\'{}\''.format(v))
|
||||
func_params.append('{}={}'.format(k, v))
|
||||
|
||||
func_params = ','.join(func_params).strip(',')
|
||||
|
||||
tmp_run_file = tempfile.NamedTemporaryFile(suffix='.py').name
|
||||
tmp_res_file = tempfile.NamedTemporaryFile(suffix='.pkl').name
|
||||
|
||||
with open(tmp_run_file, 'w') as f:
|
||||
print('save temporary run file to : {}'.format(tmp_run_file))
|
||||
print('save results to : {}'.format(tmp_res_file))
|
||||
run_file_content = _DIST_SCRIPT_TEMPLATE.format(script_name, script_name, func_name, func_params)
|
||||
f.write(run_file_content)
|
||||
|
||||
tmp_res_files = []
|
||||
if save_all_ranks:
|
||||
for i in range(num_gpus):
|
||||
tmp_res_files.append(tmp_res_file + str(i))
|
||||
else:
|
||||
tmp_res_files = [tmp_res_file]
|
||||
self.addCleanup(self.clean_tmp, [tmp_run_file] + tmp_res_files)
|
||||
|
||||
tmp_env = copy.deepcopy(os.environ)
|
||||
tmp_env['PYTHONPATH'] = ':'.join((tmp_env.get('PYTHONPATH', ''), script_dir)).lstrip(':')
|
||||
# avoid distributed test hang
|
||||
tmp_env['NCCL_P2P_DISABLE'] = '1'
|
||||
script_params = '--save_all_ranks=%s --save_file=%s' % (save_all_ranks, tmp_res_file)
|
||||
script_cmd = '%s %s %s' % (dist_start_cmd, tmp_run_file, script_params)
|
||||
print('script command: %s' % script_cmd)
|
||||
res = subprocess.call(script_cmd, shell=True, env=tmp_env)
|
||||
|
||||
script_res = []
|
||||
for res_file in tmp_res_files:
|
||||
with open(res_file, 'rb') as f:
|
||||
script_res.append(pickle.load(f))
|
||||
if not save_all_ranks:
|
||||
script_res = script_res[0]
|
||||
|
||||
if assert_callback:
|
||||
assert_callback(script_res)
|
||||
|
||||
self.assertEqual(res, 0, msg='The test function ``{}`` in ``{}`` run failed!'.format(func_name, script_name))
|
||||
|
||||
return script_res
|
||||
|
||||
def start(self, func, num_gpus, assert_callback=None, save_all_ranks=False, *args, **kwargs):
|
||||
from .torch_utils import _find_free_port
|
||||
ip = socket.gethostbyname(socket.gethostname())
|
||||
if 'dist_start_cmd' in kwargs:
|
||||
dist_start_cmd = kwargs.pop('dist_start_cmd')
|
||||
else:
|
||||
dist_start_cmd = '%s -m torch.distributed.launch --nproc_per_node=%d ' \
|
||||
'--master_addr=\'%s\' --master_port=%s' % (sys.executable, num_gpus, ip, _find_free_port())
|
||||
|
||||
return self._start(
|
||||
dist_start_cmd=dist_start_cmd,
|
||||
func=func,
|
||||
num_gpus=num_gpus,
|
||||
assert_callback=assert_callback,
|
||||
save_all_ranks=save_all_ranks,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
def clean_tmp(self, tmp_file_list):
|
||||
for file in tmp_file_list:
|
||||
if os.path.exists(file):
|
||||
if os.path.isdir(file):
|
||||
shutil.rmtree(file)
|
||||
else:
|
||||
os.remove(file)
|
||||
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
|
||||
|
||||
def test_channel():
|
||||
from swift import SftArguments, sft_main
|
||||
sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2.5-7B-Instruct',
|
||||
dataset=['channel.jsonl#1000'],
|
||||
split_dataset_ratio=0.01,
|
||||
enable_channel_loss=True,
|
||||
packing=True,
|
||||
max_length=128,
|
||||
attn_impl='flash_attn',
|
||||
load_from_cache_file=False,
|
||||
deepspeed='zero2',
|
||||
eval_steps=5))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_channel()
|
||||
@@ -0,0 +1,61 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'per_device_eval_batch_size': 2,
|
||||
'save_steps': 50,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_llm():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2.5-1.5B-Instruct',
|
||||
tuner_type='lora',
|
||||
num_labels=2,
|
||||
dataset=['DAMO_NLP/jd:cls#2000'],
|
||||
split_dataset_ratio=0.01,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True))
|
||||
|
||||
|
||||
def test_bert():
|
||||
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='answerdotai/ModernBERT-base',
|
||||
# model='iic/nlp_structbert_backbone_base_std',
|
||||
tuner_type='full',
|
||||
num_labels=2,
|
||||
dataset=['DAMO_NLP/jd:cls#2000'],
|
||||
split_dataset_ratio=0.01,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(model=last_model_checkpoint, load_data_args=True))
|
||||
|
||||
|
||||
def test_mllm():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='OpenGVLab/InternVL2-1B',
|
||||
tuner_type='lora',
|
||||
num_labels=2,
|
||||
dataset=['DAMO_NLP/jd:cls#500'],
|
||||
split_dataset_ratio=0.01,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_llm()
|
||||
# test_bert()
|
||||
test_mllm()
|
||||
@@ -0,0 +1,88 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 4,
|
||||
'save_steps': 5,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_embedding():
|
||||
from swift import SftArguments, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen3-Embedding-0.6B',
|
||||
task_type='embedding',
|
||||
dataset=['sentence-transformers/stsb:positive'],
|
||||
split_dataset_ratio=0.01,
|
||||
load_from_cache_file=False,
|
||||
loss_type='infonce',
|
||||
attn_impl='flash_attn',
|
||||
max_length=2048,
|
||||
**kwargs,
|
||||
))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
print(f'last_model_checkpoint: {last_model_checkpoint}')
|
||||
|
||||
|
||||
def test_reranker():
|
||||
from swift import SftArguments, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen3-Reranker-4B',
|
||||
tuner_type='lora',
|
||||
load_from_cache_file=True,
|
||||
task_type='generative_reranker',
|
||||
dataset=['MTEB/scidocs-reranking#10000'],
|
||||
split_dataset_ratio=0.05,
|
||||
loss_type='pointwise_reranker',
|
||||
dataloader_drop_last=True,
|
||||
eval_strategy='steps',
|
||||
eval_steps=10,
|
||||
max_length=4096,
|
||||
attn_impl='flash_attn',
|
||||
num_train_epochs=1,
|
||||
save_steps=200,
|
||||
per_device_train_batch_size=2,
|
||||
per_device_eval_batch_size=2,
|
||||
gradient_accumulation_steps=8,
|
||||
dataset_num_proc=2,
|
||||
))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
print(f'last_model_checkpoint: {last_model_checkpoint}')
|
||||
|
||||
|
||||
def test_reranker2():
|
||||
from swift import SftArguments, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2.5-VL-3B-Instruct',
|
||||
tuner_type='lora',
|
||||
load_from_cache_file=True,
|
||||
task_type='reranker',
|
||||
dataset=['MTEB/scidocs-reranking'],
|
||||
split_dataset_ratio=0.05,
|
||||
loss_type='listwise_reranker',
|
||||
dataloader_drop_last=True,
|
||||
eval_strategy='steps',
|
||||
eval_steps=10,
|
||||
max_length=4096,
|
||||
attn_impl='flash_attn',
|
||||
padding_side='right',
|
||||
num_train_epochs=1,
|
||||
save_steps=200,
|
||||
per_device_train_batch_size=2,
|
||||
per_device_eval_batch_size=2,
|
||||
gradient_accumulation_steps=8,
|
||||
dataset_num_proc=1,
|
||||
))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
print(f'last_model_checkpoint: {last_model_checkpoint}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_embedding()
|
||||
test_reranker()
|
||||
@@ -0,0 +1,27 @@
|
||||
def test_export_cached_dataset():
|
||||
from swift import ExportArguments, export_main
|
||||
export_main(
|
||||
ExportArguments(
|
||||
model='Qwen/Qwen2.5-7B-Instruct',
|
||||
dataset='swift/Chinese-Qwen3-235B-2507-Distill-data-110k-SFT',
|
||||
to_cached_dataset=True,
|
||||
dataset_num_proc=4,
|
||||
))
|
||||
print()
|
||||
|
||||
|
||||
def test_sft():
|
||||
from swift import SftArguments, sft_main
|
||||
sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2.5-7B-Instruct',
|
||||
dataset='liucong/Chinese-DeepSeek-R1-Distill-data-110k-SFT#1000',
|
||||
dataset_num_proc=2,
|
||||
packing=True,
|
||||
attn_impl='flash_attn',
|
||||
))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_export_cached_dataset()
|
||||
test_sft()
|
||||
@@ -0,0 +1,89 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'save_steps': 5,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_full_vit():
|
||||
os.environ['MAX_PIXELS'] = '100352'
|
||||
os.environ['SIZE_FACTOR'] = '12'
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2-VL-7B-Instruct',
|
||||
dataset=['modelscope/coco_2014_caption:validation#20', 'AI-ModelScope/alpaca-gpt4-data-en#20'],
|
||||
split_dataset_ratio=0.01,
|
||||
tuner_type='full',
|
||||
freeze_llm=True,
|
||||
freeze_vit=False,
|
||||
freeze_aligner=True,
|
||||
**kwargs))
|
||||
|
||||
|
||||
def test_full_aligner():
|
||||
os.environ['MAX_PIXELS'] = '100352'
|
||||
os.environ['SIZE_FACTOR'] = '12'
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2-VL-7B-Instruct',
|
||||
dataset=['modelscope/coco_2014_caption:validation#20', 'AI-ModelScope/alpaca-gpt4-data-en#20'],
|
||||
split_dataset_ratio=0.01,
|
||||
tuner_type='full',
|
||||
freeze_llm=True,
|
||||
freeze_vit=True,
|
||||
freeze_aligner=False,
|
||||
**kwargs))
|
||||
|
||||
|
||||
def test_lora_vit():
|
||||
os.environ['MAX_PIXELS'] = '100352'
|
||||
os.environ['SIZE_FACTOR'] = '12'
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2-VL-7B-Instruct',
|
||||
dataset=['modelscope/coco_2014_caption:validation#20', 'AI-ModelScope/alpaca-gpt4-data-en#20'],
|
||||
split_dataset_ratio=0.01,
|
||||
tuner_type='lora',
|
||||
freeze_llm=True,
|
||||
freeze_vit=False,
|
||||
freeze_aligner=True,
|
||||
**kwargs))
|
||||
|
||||
|
||||
def test_lora_aligner():
|
||||
os.environ['MAX_PIXELS'] = '100352'
|
||||
os.environ['SIZE_FACTOR'] = '12'
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2-VL-7B-Instruct',
|
||||
dataset=['modelscope/coco_2014_caption:validation#20', 'AI-ModelScope/alpaca-gpt4-data-en#20'],
|
||||
split_dataset_ratio=0.01,
|
||||
tuner_type='lora',
|
||||
freeze_llm=True,
|
||||
freeze_vit=True,
|
||||
freeze_aligner=False,
|
||||
**kwargs))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_full_vit()
|
||||
test_full_aligner()
|
||||
# test_lora_vit()
|
||||
# test_lora_aligner()
|
||||
@@ -0,0 +1,80 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 4,
|
||||
'save_steps': 5,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_llm():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='gkd',
|
||||
model='Qwen/Qwen2.5-0.5B',
|
||||
teacher_model='Qwen/Qwen2.5-1.5B-Instruct',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-en#2000'],
|
||||
split_dataset_ratio=0.01,
|
||||
load_from_cache_file=False,
|
||||
seq_kd=True,
|
||||
**kwargs,
|
||||
))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_mllm():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='gkd',
|
||||
model='OpenGVLab/InternVL3-2B-Pretrained',
|
||||
teacher_model='OpenGVLab/InternVL3-8B',
|
||||
dataset=['AI-ModelScope/LaTeX_OCR#2000', 'AI-ModelScope/alpaca-gpt4-data-en#2000'],
|
||||
split_dataset_ratio=0.01,
|
||||
load_from_cache_file=False,
|
||||
**kwargs,
|
||||
))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_multi_turn():
|
||||
"""GKD multi-turn smoke test: verify rollout → encode → loss works with multi_turn_scheduler.
|
||||
|
||||
Uses the built-in ``math_tip_trick`` scheduler with max_turns=2 to keep the test
|
||||
lightweight. The key assertion is that training completes without raising
|
||||
NotImplementedError (the previous block) and that multi-turn response token ids
|
||||
are correctly propagated through the GKD loss pipeline.
|
||||
"""
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='gkd',
|
||||
model='Qwen/Qwen2.5-0.5B',
|
||||
teacher_model='Qwen/Qwen2.5-1.5B-Instruct',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-en#200'],
|
||||
split_dataset_ratio=0.01,
|
||||
load_from_cache_file=False,
|
||||
multi_turn_scheduler='math_tip_trick',
|
||||
max_turns=2,
|
||||
max_completion_length=256,
|
||||
num_generations=2,
|
||||
per_device_train_batch_size=2,
|
||||
gradient_accumulation_steps=1,
|
||||
save_steps=50,
|
||||
num_train_epochs=1,
|
||||
))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
if last_model_checkpoint is not None:
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_llm()
|
||||
# test_mllm()
|
||||
test_multi_turn()
|
||||
@@ -0,0 +1,9 @@
|
||||
import os
|
||||
|
||||
from swift import SftArguments, sft_main
|
||||
|
||||
os.environ['MAX_PIXELS'] = str(16 * 28 * 28)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sft_main(
|
||||
SftArguments(model='Qwen/Qwen2.5-VL-7B-Instruct', dataset='AI-ModelScope/coco#2000', split_dataset_ratio=0.01))
|
||||
@@ -0,0 +1,153 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'per_device_eval_batch_size': 2,
|
||||
'save_steps': 50,
|
||||
'gradient_accumulation_steps': 1,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
SYSTEM_PROMPT = ('A conversation between User and Assistant. The user asks a question, and the Assistant solves it. '
|
||||
'The assistant first thinks about the reasoning process in the mind and then provides the user '
|
||||
'with the answer. The reasoning process and answer are enclosed within <think> </think> '
|
||||
'and <answer> </answer> tags, respectively, i.e., <think> reasoning process here </think><answer> '
|
||||
'answer here </answer>')
|
||||
|
||||
|
||||
def test_llm():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='grpo',
|
||||
model='Qwen/Qwen2.5-1.5B-Instruct',
|
||||
tuner_type='full',
|
||||
dataset=['AI-MO/NuminaMath-TIR#100'],
|
||||
split_dataset_ratio=0.1,
|
||||
system=SYSTEM_PROMPT,
|
||||
reward_funcs=['accuracy', 'format'],
|
||||
max_completion_length=4096,
|
||||
num_generations=2,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_llm_zero2():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='grpo',
|
||||
model='Qwen/Qwen2.5-1.5B-Instruct',
|
||||
tuner_type='full',
|
||||
dataset=['AI-MO/NuminaMath-TIR#100'],
|
||||
system=SYSTEM_PROMPT,
|
||||
reward_funcs=['accuracy', 'format'],
|
||||
max_completion_length=4096,
|
||||
num_generations=2,
|
||||
deepspeed='zero2',
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_llm_vllm():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='grpo',
|
||||
model='Qwen/Qwen2.5-1.5B-Instruct',
|
||||
reward_model='AI-ModelScope/GRM_Llama3.1_8B_rewardmodel-ft',
|
||||
tuner_type='full',
|
||||
dataset=['AI-MO/NuminaMath-TIR#100'],
|
||||
system=SYSTEM_PROMPT,
|
||||
reward_funcs=['accuracy', 'format'],
|
||||
use_vllm=True,
|
||||
max_completion_length=4096,
|
||||
num_generations=2,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_llm_vllm_zero2():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='grpo',
|
||||
model='Qwen/Qwen2.5-1.5B-Instruct',
|
||||
tuner_type='full',
|
||||
dataset=['AI-MO/NuminaMath-TIR#100'],
|
||||
system=SYSTEM_PROMPT,
|
||||
reward_funcs=['accuracy', 'format'],
|
||||
use_vllm=True,
|
||||
max_completion_length=4096,
|
||||
num_generations=2,
|
||||
deepspeed='zero2',
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_mllm_pt():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='grpo',
|
||||
model='Qwen/Qwen2-VL-2B-Instruct',
|
||||
tuner_type='full',
|
||||
# dataset=['AI-MO/NuminaMath-TIR#100'],
|
||||
dataset=['modelscope/coco_2014_caption:validation#100'],
|
||||
system=SYSTEM_PROMPT,
|
||||
reward_funcs=['format'],
|
||||
max_completion_length=4096,
|
||||
num_generations=2,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_grpo_minimal():
|
||||
import trl
|
||||
from packaging import version
|
||||
if version.parse(trl.__version__) < version.parse('0.26'):
|
||||
print(f'Skipping test_grpo_minimal: trl>=0.26 required, found trl=={trl.__version__}')
|
||||
return
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='grpo',
|
||||
model='Qwen/Qwen2-0.5B',
|
||||
tuner_type='lora',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#20'],
|
||||
system=SYSTEM_PROMPT,
|
||||
reward_funcs=['format'],
|
||||
max_completion_length=128,
|
||||
num_generations=2,
|
||||
max_steps=2,
|
||||
per_device_train_batch_size=2,
|
||||
gradient_accumulation_steps=1,
|
||||
save_steps=2,
|
||||
split_dataset_ratio=0.01,
|
||||
logging_steps=1,
|
||||
use_vllm=False,
|
||||
**{
|
||||
k: v
|
||||
for k, v in kwargs.items() if k not in [
|
||||
'per_device_train_batch_size', 'save_steps', 'gradient_accumulation_steps', 'num_train_epochs',
|
||||
'per_device_eval_batch_size'
|
||||
]
|
||||
}))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_llm()
|
||||
# test_llm_zero3()
|
||||
# test_llm_vllm()
|
||||
# test_llm_vllm_zero2()
|
||||
test_mllm_pt()
|
||||
# test_grpo_minimal()
|
||||
@@ -0,0 +1,41 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'save_steps': 5,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_llm():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='kto',
|
||||
model='Qwen/Qwen2-7B-Instruct',
|
||||
dataset=['AI-ModelScope/ultrafeedback-binarized-preferences-cleaned-kto#100'],
|
||||
split_dataset_ratio=0.01,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_mllm():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='kto',
|
||||
model='Qwen/Qwen2-VL-7B-Instruct',
|
||||
dataset=['AI-ModelScope/ultrafeedback-binarized-preferences-cleaned-kto#100'],
|
||||
split_dataset_ratio=0.01,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_llm()
|
||||
test_mllm()
|
||||
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0,1'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'save_steps': 30,
|
||||
'gradient_accumulation_steps': 2,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_sft():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2.5-7B-Instruct',
|
||||
dataset=['swift/self-cognition#200'],
|
||||
split_dataset_ratio=0.01,
|
||||
use_liger_kernel=True,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True))
|
||||
|
||||
|
||||
def test_mllm_dpo():
|
||||
os.environ['MAX_PIXLES'] = f'{1280 * 28 * 28}'
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='dpo',
|
||||
model='Qwen/Qwen2.5-VL-3B-Instruct',
|
||||
tuner_type='full',
|
||||
dataset=['swift/RLAIF-V-Dataset#1000'],
|
||||
split_dataset_ratio=0.01,
|
||||
dataset_num_proc=8,
|
||||
deepspeed='zero3',
|
||||
use_liger_kernel=True,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(model=last_model_checkpoint, load_data_args=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_sft()
|
||||
# test_mllm_dpo()
|
||||
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'per_device_eval_batch_size': 2,
|
||||
'save_steps': 50,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_reg_llm():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2.5-1.5B-Instruct',
|
||||
tuner_type='lora',
|
||||
num_labels=1,
|
||||
dataset=['sentence-transformers/stsb:reg#200'],
|
||||
split_dataset_ratio=0.01,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, metric='acc'))
|
||||
|
||||
|
||||
def test_reg_mllm():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
|
||||
# OpenGVLab/InternVL2-1B
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2-VL-2B-Instruct',
|
||||
tuner_type='lora',
|
||||
num_labels=1,
|
||||
dataset=['sentence-transformers/stsb:reg#200'],
|
||||
split_dataset_ratio=0.01,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, metric='acc'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_reg_llm()
|
||||
test_reg_mllm()
|
||||
@@ -0,0 +1,48 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
|
||||
os.environ['NPROC_PER_NODE'] = '2'
|
||||
|
||||
|
||||
def train():
|
||||
from swift import RLHFArguments, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='gkd',
|
||||
model='Qwen/Qwen3.5-4B',
|
||||
teacher_model='Qwen/Qwen3.5-4B',
|
||||
tuner_type='lora',
|
||||
lora_rank=64,
|
||||
lora_alpha=128,
|
||||
target_modules=['all-linear'],
|
||||
use_vllm=True,
|
||||
vllm_mode='colocate',
|
||||
vllm_gpu_memory_utilization=0.7,
|
||||
vllm_max_model_len=10240,
|
||||
sleep_level=1,
|
||||
external_plugins=['examples/train/rlhf/opsd/opsd_plugin.py'],
|
||||
dataset=['open-r1/OpenThoughts-114k-math'],
|
||||
lmbda=1.0,
|
||||
beta=0.5,
|
||||
temperature=1.2,
|
||||
sft_alpha=0,
|
||||
torch_dtype='bfloat16',
|
||||
max_steps=1000,
|
||||
per_device_train_batch_size=4,
|
||||
gradient_accumulation_steps=1,
|
||||
learning_rate=2e-5,
|
||||
save_steps=100,
|
||||
save_total_limit=10,
|
||||
logging_steps=1,
|
||||
max_length=8192,
|
||||
max_completion_length=2048,
|
||||
save_only_model=True,
|
||||
gradient_checkpointing=True,
|
||||
deepspeed='zero0',
|
||||
attn_impl='flash_attn',
|
||||
))
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
train()
|
||||
@@ -0,0 +1,66 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'save_steps': 50,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 3,
|
||||
}
|
||||
|
||||
|
||||
def test_llm():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2-7B-Instruct',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#1000', 'swift/self-cognition#1000'],
|
||||
split_dataset_ratio=0.01,
|
||||
packing=True,
|
||||
max_length=4096,
|
||||
attn_impl='flash_attn',
|
||||
logging_steps=1,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_streaming():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2-7B-Instruct',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#10000'],
|
||||
packing=True,
|
||||
max_length=4096,
|
||||
streaming=True,
|
||||
attn_impl='flash_attn',
|
||||
max_steps=100,
|
||||
dataset_num_proc=1,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_mllm_streaming():
|
||||
from swift import InferArguments, SftArguments, infer_main, sft_main
|
||||
result = sft_main(
|
||||
SftArguments(
|
||||
model='Qwen/Qwen2.5-VL-7B-Instruct',
|
||||
dataset=['AI-ModelScope/LaTeX_OCR#20000'],
|
||||
packing=True,
|
||||
max_length=8192,
|
||||
streaming=True,
|
||||
attn_impl='flash_attn',
|
||||
max_steps=100,
|
||||
dataset_num_proc=4,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_llm()
|
||||
# test_streaming()
|
||||
test_mllm_streaming()
|
||||
@@ -0,0 +1,41 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'save_steps': 5,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_rm():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='rm',
|
||||
model='Shanghai_AI_Laboratory/internlm2-1_8b-reward',
|
||||
dataset=['hjh0119/shareAI-Llama3-DPO-zh-en-emoji#100'],
|
||||
split_dataset_ratio=0.01,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_ppo():
|
||||
from swift import InferArguments, RLHFArguments, infer_main, rlhf_main
|
||||
result = rlhf_main(
|
||||
RLHFArguments(
|
||||
rlhf_type='ppo',
|
||||
model='LLM-Research/Llama-3.2-1B-Instruct',
|
||||
reward_model='AI-ModelScope/GRM-Llama3.2-3B-rewardmodel-ft',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#100', 'AI-ModelScope/alpaca-gpt4-data-en#100'],
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_rm()
|
||||
test_ppo()
|
||||
@@ -0,0 +1,58 @@
|
||||
import os
|
||||
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
|
||||
os.environ['ASCEND_RT_VISIBLE_DEVICES'] = '0'
|
||||
kwargs = {
|
||||
'per_device_train_batch_size': 2,
|
||||
'save_steps': 5,
|
||||
'gradient_accumulation_steps': 4,
|
||||
'num_train_epochs': 1,
|
||||
}
|
||||
|
||||
|
||||
def test_llm():
|
||||
from swift import InferArguments, PretrainArguments, infer_main, pretrain_main
|
||||
result = pretrain_main(
|
||||
PretrainArguments(
|
||||
model='Qwen/Qwen2-7B-Instruct', dataset=['swift/sharegpt:all#100'], split_dataset_ratio=0.01, **kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_mllm():
|
||||
from swift import InferArguments, PretrainArguments, infer_main, pretrain_main
|
||||
result = pretrain_main(
|
||||
PretrainArguments(
|
||||
model='Qwen/Qwen2-VL-7B-Instruct',
|
||||
dataset=['modelscope/coco_2014_caption:validation#20', 'AI-ModelScope/alpaca-gpt4-data-en#20'],
|
||||
split_dataset_ratio=0.01,
|
||||
**kwargs))
|
||||
last_model_checkpoint = result['last_model_checkpoint']
|
||||
infer_main(InferArguments(adapters=last_model_checkpoint, load_data_args=True, merge_lora=True))
|
||||
|
||||
|
||||
def test_pretrain_minimal():
|
||||
from swift import PretrainArguments, pretrain_main
|
||||
result = pretrain_main(
|
||||
PretrainArguments(
|
||||
model='Qwen/Qwen2-0.5B',
|
||||
dataset=['AI-ModelScope/alpaca-gpt4-data-zh#20'],
|
||||
max_steps=2,
|
||||
per_device_train_batch_size=1,
|
||||
gradient_accumulation_steps=1,
|
||||
save_steps=2,
|
||||
split_dataset_ratio=0.01,
|
||||
tuner_type='lora',
|
||||
logging_steps=1,
|
||||
**{
|
||||
k: v
|
||||
for k, v in kwargs.items() if k not in
|
||||
['per_device_train_batch_size', 'save_steps', 'gradient_accumulation_steps', 'num_train_epochs']
|
||||
}))
|
||||
assert os.path.isdir(result['last_model_checkpoint'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# test_llm()
|
||||
test_mllm()
|
||||
# test_pretrain_minimal()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user