diff --git a/openseek/competition/LongContext-ICL-Annotation/READMD_cn.md b/openseek/competition/LongContext-ICL-Annotation/READMD_cn.md deleted file mode 100644 index ffee7ca0..00000000 --- a/openseek/competition/LongContext-ICL-Annotation/READMD_cn.md +++ /dev/null @@ -1,71 +0,0 @@ -# 超长长上下文场景中LLM自动数据标注挑战赛 - ---- - -## 消息 - -- **[2026-01-20] `发布`:** 赛事信息已在 **Kaggle** 正式上线。详情见:[FlagOS Open Computing Global Challenge](https://www.kaggle.com/competitions/flag-os-open-computing-global-challenge). -- **[2026-01-06] `发布`:** 由 **众智 FlagOS 社区**、**北京智源人工智能研究院(BAAI)** 与 **CCF ODTC** 联合主办的综合性大赛 **FlagOS 开放计算全球挑战赛** 正式发布。详情见: - [FlagOS开放计算全球挑战赛- AI赛事通 | 数据算法赛](https://www.competehub.dev/zh/competitions/modelscope180) - - ---- - - -## 快速开始 -### 1. 环境 - -```bash -openai -torch -flagScale -``` - -### 2. 下载模型权重 -```bash -hf download Qwen/Qwen3-4B --local-dir Qwen3-4B -# or -modelscope download --model Qwen/Qwen3-4B -``` -### 3. 长文本配置 -在`Qwen3-4B/config.json`将原有配置替换为: -```json -"rope_scaling": { - "rope_type": "yarn", - "factor": 4.0, - "original_max_position_embeddings": 32768 -} -``` -### 4. 模型部署 - -请根据实际需求,配置 `llm_config.yaml` 文件。启动配置 - -```bash -cd FlagScale -python run.py --config-path .. --config-name llm_config action=run -``` - -在模型服务启动后,可通过以下方式测试本地 API: - -```bash -python api_test.py -``` - -如需停止服务,请执行: - -```bash -python run.py --config-path .. --config-name llm_config action=stop -``` - -### 5. 运行/改进基线方法(Baseline) - -启动如下命令开始模型标注 -```bash -python main.py -``` - -实现新的标注方法,请修改`method.py`文件。你可以在该文件中: -* 定义新的指令模板、 -* 定义新的上下文示例选择策略 -* 定义新的模型推理、标注方案 -* 添加自定义后处理逻辑 diff --git a/openseek/competition/LongContext-ICL-Annotation/README.md b/openseek/competition/LongContext-ICL-Annotation/README.md index c52275b6..cc037199 100644 --- a/openseek/competition/LongContext-ICL-Annotation/README.md +++ b/openseek/competition/LongContext-ICL-Annotation/README.md @@ -26,7 +26,7 @@ flagScale ### 2. Download Model Weights ```bash -hf download Qwen/Qwen3-4B --local-dir Qwen3-4B +hf download Qwen/Qwen3-4B --local-dir ~/.cache/modelscope/hub/models/Qwen/Qwen3-4B # or modelscope download --model Qwen/Qwen3-4B ``` @@ -48,6 +48,7 @@ In `Qwen3-4B/config.json`, replace the original configuration with the following Configure the `llm_config.yaml` file according to your actual requirements. Then start the service with: ```bash +git clone https://gitee.com/flagos-ai/FlagScale cd FlagScale python run.py --config-path .. --config-name llm_config action=run ``` @@ -69,7 +70,8 @@ python run.py --config-path .. --config-name llm_config action=stop Start the baseline annotation pipeline with: ```bash -python main.py +cd src +python main.py --task_id=0 --task_step=0 ``` To implement a new annotation method, modify the `method.py` file. Within this file, you may: @@ -78,3 +80,18 @@ To implement a new annotation method, modify the `method.py` file. Within this f - Design new context example selection strategies - Implement alternative model inference and annotation pipelines - Add custom post-processing logic + + +The table below is the release compatibility matrix for vLLM Ascend release. +```csv +vLLM Ascend vLLM Python CANN PyTorch/torch_npu Triton Ascend Recommend +v0.14.0rc1 v0.14.1 >= 3.10, < 3.12 8.5.0 2.9.0 / 2.9.0 3.2.0 Y +v0.13.0 v0.13.0 >= 3.10, < 3.12 8.5.0 2.9.0 / 2.8.0.post2 3.2.0 N +v0.13.0rc2 v0.13.0 >= 3.10, < 3.12 8.5.0 2.8.0 / 2.8.0.post1 3.2.0 N +v0.13.0rc1 v0.13.0 >= 3.10, < 3.12 8.3.RC2 2.8.0 / 2.8.0 3.2.0 Y +v0.12.0rc1 v0.12.0 >= 3.10, < 3.12 8.3.RC2 2.8.0 / 2.8.0 3.2.0 Y +v0.11.0 v0.11.0 >= 3.9 , < 3.12 8.3.RC2 2.7.1 / 2.7.1.post1 3.2.0 N +v0.11.0rc3 v0.11.0 >= 3.9, < 3.12 8.3.RC2 2.7.1 / 2.7.1.post1 3.2.0 N +v0.11.0rc2 v0.11.0 >= 3.9, < 3.12 8.3.RC2 2.7.1 / 2.7.1 3.2.0 Y +v0.11.0rc1 v0.11.0 >= 3.9, < 3.12 8.3.RC1 2.7.1 / 2.7.1 3.2.0 Y +``` diff --git a/openseek/competition/LongContext-ICL-Annotation/README_cn.md b/openseek/competition/LongContext-ICL-Annotation/README_cn.md new file mode 100644 index 00000000..b28d40d9 --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/README_cn.md @@ -0,0 +1,167 @@ +# 超长长上下文场景中LLM自动数据标注挑战赛 + +--- + +## 消息 + +- **[2026-01-20] `发布`:** 赛事信息已在 **Kaggle** 正式上线。详情见:[FlagOS Open Computing Global Challenge](https://www.kaggle.com/competitions/flag-os-open-computing-global-challenge). +- **[2026-01-06] `发布`:** 由 **众智 FlagOS 社区**、**北京智源人工智能研究院(BAAI)** 与 **CCF ODTC** 联合主办的综合性大赛 **FlagOS 开放计算全球挑战赛** 正式发布。详情见: + [FlagOS开放计算全球挑战赛- AI赛事通 | 数据算法赛](https://www.competehub.dev/zh/competitions/modelscope180) + + +--- + + +## 快速开始 +### 1. 环境 + +```bash +openai +torch +flagScale +``` + +### 2. 下载模型权重 +```bash +hf download Qwen/Qwen3-4B --local-dir /root/.cache/modelscope/hub/models/Qwen/Qwen3-4B +# or +modelscope download --model Qwen/Qwen3-4B +``` + +### 3. 长文本配置 +在`Qwen3-4B/config.json`将原有配置替换为: +```json +"rope_scaling": { + "rope_type": "yarn", + "factor": 4.0, + "original_max_position_embeddings": 32768 +} +``` + +### 4. 模型部署 +安装环境和下载模型 +```bash +pip install -r requirements.txt +bash create_env_ascend.sh +``` + +请根据实际需求,配置 `llm_config.yaml` 文件,启动服务 +```bash +bash start-qwen3_4_serve.sh +``` + +在模型服务启动后,可通过以下方式测试本地 API: +```bash +python api_test.py +``` + +如需停止服务,请执行: +```bash +bash stop-qwen3_4_serve.sh +``` + +### 5. 运行/改进基线方法(Baseline) +如需开始模型标注,启动如下命令 +```bash +bash start-all-tasks_on_4_serve.sh +``` + +如需结束模型标注,启动如下命令 +```bash +bash stop-all-tasks.sh +``` + +### 6. 安装环境补充说明 +```sh +# 系统:比赛官方提供的云服务器 (为了加速实验 - 建议用更多的卡数,推荐用8个) +# GPU:Ascend 910C x 2 卡 +# CPU:20核 +# 磁盘:200G +# 内存:80G +# NPU:CANN8.3.RC2 + +# 检查基础环境: +cat /usr/local/Ascend/ascend-toolkit/latest/version.cfg +cat /usr/local/Ascend/ascend-toolkit/set_env.sh + +# 6.1、安装环境(flagscale所需CANN、vllm、vllm-ascend版本依赖请按实际环境调整) +DIR=your-project-path/OpenSeek/openseek/competition/LongContext-ICL-Annotation/src +cd $DIR +pip install -r $DIR/requirements.txt +bash create_env_ascend.sh + +# 6.2、启动服务(启动服务后需要耐心等一小段时间,让模型服务完全就绪,可用npu-smi或nputop查看状态) +DIR=your-project-path/OpenSeek/openseek/competition/LongContext-ICL-Annotation/src +cd $DIR +# 单机多卡启动 ,若超过2张NPU,请调整llm_config.yaml的参数:tensor_parallel_size数值与ASCEND_RT_VISIBLE_DEVICES数值 +VLLM_PLUGINS=fl flagscale server qwen3 --config $DIR/llm_config.yaml +# 或者 +bash start-qwen3-server.sh +``` + +### 7. 执行命令补充说明 +#### 7.1) 参数 +```bash +--task_id=0, 遍历处理所有任务 +--task_id=$id,(one of [1,2,3,4,5,6,7,8]),则只处理对应的一个任务 +--task_step=1,则利用ICL样本提取关键操作Operation +--task_step=2,则利用Operation获取预测结果Result +--task_step=0,则首先利用ICL提取Operation,然后利用Operation获取Result +``` + +#### 7.2) 入口文件 +```bash +main.py 总是每次同步提交一个批次,便于前台调试查看过程, + 例如: + DEBUG_PRINT_STREAM=1 python main.py --task_id=5 --task_step=1 + DEBUG_PRINT_STREAM=2 python main.py --task_id=5 --task_step=1 +main_batch.py 可以每次异步并发提交多个批次,用于后台命令完成任务,与服务端张量和数据并行计算协同,加速实验进度 + 例如: (不要设置DEBUG_PRINT_STREAM=1,因为多个异步并发请求,实时输出是混乱无序的) + REQ_CONCURRENCY_NUM=8 DEBUG_PRINT_STREAM=0 python main_batch.py --task_id=5 --task_step=1 + DEBUG_PRINT_STREAM=2 python main_batch.py --task_id=5 --task_step=1 +``` + +#### 7.3) 如果你采用了更优的自定义配置,只需设置以下环境变量(可按实际修改默认值): +```bash +export SERVE_API_KEY="" +export SERVE_BASE_URL="http://localhost:2026/v1" +export SERVE_MODEL_NAME="Qwen/Qwen3-4B" +export SERVE_TOKENIZER_PATH=~/.cache/modelscope/hub/models/Qwen/Qwen3-4B +export REQ_CONCURRENCY_NUM=2 +export ICL_REPEAT_N=1 +# +# REQ_CONCURRENCY_NUM 默认2,客户端API并发请求数,如果卡很多,可以增加该数值,与服务端张量和数据并行计算协同,加快实验进度 +# ICL_REPEAT_N 默认1,在提取Opration时对ICL的重复次数,如果卡很多,可以增加该数值,提高Opration的完整性 +# +``` + +#### 7.4) 执行命令分析解读:需要考虑这8个任务数据kv缓存的相互干扰因素 +DIR=your-project-path/OpenSeek/openseek/competition/LongContext-ICL-Annotation/src +##### 7.4.1)最佳执行命令(推荐) +在卡少时可以用a或b方式,本人`单机2卡部署成4个服务(每卡2个服务)`使用c方式 (为了在并发时让不同任务KV缓存隔离,未使用max_num_seqs>1 tensor_parallel_size>1的方案) +```bash +# (a方式) 1个服务,逐个任务串行执行,8个任务的kv缓存相互干扰较小 +cd $DIR && bash start-qwen3_1_serve.sh +cd $DIR && bash start-all-tasks_on_1_serve.sh +# 注:也可以在每处理一个后,重启服务清除缓存排除干扰,但是有点麻烦与耗时。若有人感兴趣,可以尝试 + +# (b方式) 2个服务,两阶段分步执行(干扰比a少,速度比a快) +cd $DIR && bash start-qwen3_2_serve.sh + +# 首先在第一个服务:执行第一阶段 +cd $DIR && bash start-all-tasks_on_2_serve-step_1.sh +# 然后在第二个服务:启动自动循环监视,在发现第一步中出现第一个文件生成时,就自动开始第二阶段, 在第一阶段完成后,自动接管其服务用于处理第二阶段的其他任务 +cd $DIR && nohup bash start-all-tasks_on_2_serve-step_2.sh > s2-wait-s1.log 2>&1 & + +# (c方式) 4个服务,8个任务KV缓存在不同服务,多任务并行执行,干扰很小速度较快 +cd $DIR && bash start-qwen3_4_serve.sh +cd $DIR && bash start-all-tasks_on_4_serve.sh + +# (d方式) 8个服务,多个任务并行执行,因为8个任务的kv缓存在8个不同服务,没有相互干扰(最佳之最推荐) +# 仅作参考 +cd $DIR && bash start-all-tasks_on_8_serves.sh +``` + +##### 7.4.2) 待处理问题:证明KV缓存干扰是否明显影响稳定性与准确率) +在单个服务多个任务并行执行时,其kv缓存相互干扰因素,是否会严重影响数据结果的稳定性和准确率? +若影响越小,则标注方案部署越容易。本人没有时间证明这个问题,若有人感兴趣,可以尝试证明一下。 diff --git a/openseek/competition/LongContext-ICL-Annotation/outputs/result.zip b/openseek/competition/LongContext-ICL-Annotation/outputs/result.zip index 9c5de265..175966f0 100644 Binary files a/openseek/competition/LongContext-ICL-Annotation/outputs/result.zip and b/openseek/competition/LongContext-ICL-Annotation/outputs/result.zip differ diff --git a/openseek/competition/LongContext-ICL-Annotation/requirements.txt b/openseek/competition/LongContext-ICL-Annotation/requirements.txt new file mode 100644 index 00000000..9e5d714b --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/requirements.txt @@ -0,0 +1,11 @@ +requests +asyncio +aiohttp +openai +argparse +tqdm +transformers +scikit-build-core>=0.11 +pybind11 +ninja +cmake diff --git a/openseek/competition/LongContext-ICL-Annotation/src/api_client.py b/openseek/competition/LongContext-ICL-Annotation/src/api_client.py new file mode 100644 index 00000000..943d3d20 --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/api_client.py @@ -0,0 +1,187 @@ +import os +import re +import json +import time +import requests +import asyncio +import aiohttp + +from openai import OpenAI, AsyncOpenAI, APIConnectionError, AuthenticationError, APIError, BadRequestError + +from const import CONST_DEBUG_SHOW_STREAM, CONST_DEBUG_SHOW_ANSWER + + +serve_api_key = os.environ.get('SERVE_API_KEY', '') +serve_base_url = os.environ.get('SERVE_BASE_URL', 'http://localhost:2026/v1') +serve_model_name = os.environ.get('SERVE_MODEL_NAME', 'Qwen/Qwen3-4B') +try: + debug_print_stream = int(os.environ.get('DEBUG_PRINT_STREAM', 0)) +except ValueError: + debug_print_stream = 0 + + +# 文件目录: {your-project-path}/OpenSeek/openseek/competition/LongContext-ICL-Annotation +LongContext_ICL_Annotation_DIR = os.path.dirname(os.path.dirname(__file__)) + +llm_visit_history_filepath = os.path.join(LongContext_ICL_Annotation_DIR, 'llm_visit_history.log') + +# 记录ICL提取执行进度 +def log_llm_visit_history(*args): + with open(llm_visit_history_filepath, 'a') as f: + f.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + ' :: ' + json.dumps(args)+'\n') + + +# 使用CHAT模版请求模型服务端 +class ChatClient: + + __client = AsyncOpenAI(base_url = serve_base_url.rstrip('/'), api_key = serve_api_key, timeout = 3600) + + # 多个并发请求模型服务(流式) + @staticmethod + def batch_request_llm_api_stream(prompts: list, max_tokens=10240, temperature=0.6, enable_thinking=True): + answers = [] + texts = asyncio.run(ChatClient.__batch_request_chat_api_stream(prompts, max_tokens, temperature, enable_thinking)) + log_llm_visit_history('batch_request_llm_api_stream-p1', '__batch_request_chat_api_stream', 'state', [item[1] for item in texts], 'max_tokens', max_tokens) + for i, (text, state) in enumerate(texts): + if debug_print_stream == str(CONST_DEBUG_SHOW_ANSWER) or debug_print_stream == CONST_DEBUG_SHOW_ANSWER: + print('text, state = ', text, state) + + # 若格式不符合要求,则多请求几次尽量提高成功率 + if state == False: + time.sleep(3) + text, state = asyncio.run(ChatClient.__request_chat_api_stream(prompts[i], max_tokens, temperature, enable_thinking, 256)) + log_llm_visit_history('batch_request_llm_api_stream-p2', '__request_chat_api_stream', 'state', state, 'max_tokens', max_tokens) + if debug_print_stream == str(CONST_DEBUG_SHOW_ANSWER) or debug_print_stream == CONST_DEBUG_SHOW_ANSWER: + print('text, state = ', text, state) + + answers.append((text, state)) + + return answers + + + # 单个请求模型服务(流式) + @staticmethod + def request_llm_api_stream(prompt: str, max_tokens=10240, temperature=0.7, enable_thinking=True): + text, state = asyncio.run(ChatClient.__request_chat_api_stream(prompt, max_tokens, temperature, enable_thinking)) + log_llm_visit_history('request_llm_api_stream-p1', '__request_chat_api_stream', 'state', state, 'max_tokens', max_tokens) + if debug_print_stream == str(CONST_DEBUG_SHOW_ANSWER) or debug_print_stream == CONST_DEBUG_SHOW_ANSWER: + print('text, state = ', text, state) + + # 若格式不符合要求,则多请求一次尽量提高成功率 + if state == False: + time.sleep(3) + text, state = asyncio.run(ChatClient.__request_chat_api_stream(prompt, max_tokens, temperature, enable_thinking, 256)) + log_llm_visit_history('request_llm_api_stream-p2', '__request_chat_api_stream', 'state', state, 'max_tokens', max_tokens) + if debug_print_stream == str(CONST_DEBUG_SHOW_ANSWER) or debug_print_stream == CONST_DEBUG_SHOW_ANSWER: + print('text, state = ', text, state) + + return text, state + + + # 并发请求模型服务(流式) + @staticmethod + async def __batch_request_chat_api_stream(prompts: list, max_tokens=10240, temperature=0.6, enable_thinking=True): + tasks = [] + for prompt in prompts: + tasks.append(ChatClient.__request_chat_api_stream(prompt, max_tokens, temperature, enable_thinking)) + + return await asyncio.gather(*tasks) + + + # 模型服务流式输出 + @staticmethod + async def __request_chat_api_stream(prompt: str, max_tokens=10240, temperature=0.6, enable_thinking=True, len_for_exception_response=0): + messages = [ + { + "role": "system", + "content": "你是一名无所不知无所不能的全能高手,请仔细理解任务描述和任务目标,展开所有联想找到最佳策略和方法,尽你所能生成用户期望的最终结果,返回结果的格式也须符合用户使用要求。" + }, + { + "role": "user", + "content": str(prompt) if enable_thinking else str(prompt) + '/no_think', # 必须强制转一次字符串 + } + ] + + try: + response = await ChatClient.__client.chat.completions.create( + model = serve_model_name, + messages = messages, + temperature = temperature, + max_tokens = max_tokens, + stream = True, + ) + + # 粗略估计字符串长度限制 + max_len = 4 * max_tokens + full_len = 0 + full_content = "" + async with response: + async for chunk in response: + content = None + if chunk.choices: + delta = chunk.choices[0].delta + if getattr(delta, 'reasoning_content', None): + content = delta.reasoning_content + elif getattr(delta, 'content', None): + content = delta.content + + if content != None: + full_content += content + # 利用 env:DEBUG_PRINT_STREAM 判断是否实时打印(调试查看模型的输出内容) + if debug_print_stream == str(CONST_DEBUG_SHOW_STREAM) or int(debug_print_stream) == CONST_DEBUG_SHOW_STREAM: + print(content, end="", flush=True) + + # 若发现长度异常,则主动终止连接提前截断输出,在这里粗略估计即可 + full_len += len(content) + if full_len > max_len: + break + + text = full_content.strip() + # print("Response-full_text:", text) + + if text.startswith(''): + idx = text.find('') + if idx > -1: + # 去掉think内容 + text = text[idx + 8 : None].strip() + else: + # 处理异常数据:胡言乱语、无限重复等等导致输出不完整 + if len_for_exception_response <= 0: + return None, False + else: + return text[ - len_for_exception_response : None ], False + + # print("Response-text:", text) + return text, True + + except APIConnectionError as e: + print(f"连接失败: {e}") + print("请检查 base_url 是否配置正确,或者网络是否正常。") + raise e + except AuthenticationError as e: + print(f"鉴权失败: {e}") + print("请检查 API Key 是否填写正确。") + raise e + except APIError as e: + error = str(e) + if "inappropriate" in error: + return 'Output data may contain inappropriate content', False + else: + raise e + except BadRequestError as e: + # Error code: 400 - Input data may contain inappropriate content. + error = str(e) + if "inappropriate" in error: + return 'Input data may contain inappropriate content', False + else: + raise e + except Exception as e: + error = str(e) + if "inappropriate" in error: + return 'Output data may contain inappropriate content', False + else: + raise e + +if __name__=="__main__": + debug_print_stream = 1 + ChatClient.request_llm_api_stream("请列举Golang中GC的原理和案例") diff --git a/openseek/competition/LongContext-ICL-Annotation/src/const.py b/openseek/competition/LongContext-ICL-Annotation/src/const.py new file mode 100644 index 00000000..6348fd71 --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/const.py @@ -0,0 +1,59 @@ +# 限制输出长度 +CONST_LIMIT_PROMPT_MAX_TOKENS = 8192 + +# 限制输出长度 +CONST_LIMIT_OUTPUT_MAX_TOKENS = 10240 + +# 调试实时打印流式输出 +CONST_DEBUG_SHOW_STREAM = 1 + +# 调试打印正文结果(排除think后的answer) +CONST_DEBUG_SHOW_ANSWER = 2 + +# 任务数据文件名称 +CONST_TASK_FILES = { + 1: 'openseek-1_closest_integers.json', + 2: 'openseek-2_count_nouns_verbs.json', + 3: 'openseek-3_collatz_conjecture.json', + 4: 'openseek-4_conala_concat_strings.json', + 5: 'openseek-5_semeval_2018_task1_tweet_sadness_detection.json', + 6: 'openseek-6_mnli_same_genre_classification.json', + 7: 'openseek-7_jeopardy_answer_generation_all.json', + 8: 'openseek-8_kernel_generation.json', +} + +# 模型输出tokens长度限制 +CONST_TASK_OUTPUT_MAX_TOKENS_SIZES = { + 1: 8192 + 8, + 2: 8192 + 8, + 3: 8192 + 128, + 4: 8192 + 128, + 5: 8192 + 16, + 6: 8192 + 16, + 7: 8192 + 128, + 8: 8192 + 2048, +} + +# 输出答案的字符串长度范围(注:既不包含think长度,也不是tokens长度) +CONST_TASK_CHECK_STRLEN_SIZES = { + 1: { 'min': 1, 'max': 8 }, + 2: { 'min': 1, 'max': 8 }, + 3: { 'min': 1, 'max': 256 }, + 4: { 'min': 1, 'max': 256 }, + 5: { 'min': 1, 'max': 32 }, + 6: { 'min': 1, 'max': 4 }, + 7: { 'min': 1, 'max': 256 }, + 8: { 'min': 384, 'max': 10240 }, +} + +# TEST预测返回类型 +CONST_TASK_TEST_RETURN_DATA_TYPES = { + 1: 'number', # 例如:12, 3, 5, -1, -0.36 等数字 + 2: 'number', + 3: 'array', # 例如: [1,2,3], ['a','b', 'c'] 等数组 + 4: 'text', # 任意英文字符串 + 5: 'text', + 6: 'text', + 7: 'phrase', # 英文短语 + 8: 'code', # 编程代码 +} diff --git a/openseek/competition/LongContext-ICL-Annotation/src/create_env_ascend.sh b/openseek/competition/LongContext-ICL-Annotation/src/create_env_ascend.sh new file mode 100644 index 00000000..51652f35 --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/create_env_ascend.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# vllm +VLLM_TARGET_DEVICE=empty pip install vllm==0.13.0 --extra-index https://download.pytorch.org/whl/cpu +# 忽略vllm依赖torch的版本提示 +pip install vllm_ascend==0.13.0rc1 +pip install vllm-plugin-fl==0.1.0+vllm0.13.0 --extra-index-url https://resource.flagos.net/repository/flagos-pypi-hosted/simple + +# FlagScale +git clone https://github.com/flagos-ai/FlagScale +cd FlagScale +pip install setuptools==82.0.0 scikit-build-core==0.11 pybind11==3.0.2 ninja==1.13.0 cmake==4.2.3 +pip install ".[ascend-serve]" -v --no-build-isolation +cd .. + +# 常用工具 +pip install ascend-nputop +pip install modelscope + +# 下载Qwen3-4B +pip install "setuptools<82.0.0" +modelscope download --model Qwen/Qwen3-4B +sed -i 's#"rope_scaling": null#"rope_scaling": {"rope_type": "yarn", "factor": 4.0, "original_max_position_embeddings": 40960}#g' ~/.cache/modelscope/hub/models/Qwen/Qwen3-4B/config.json +cat ~/.cache/modelscope/hub/models/Qwen/Qwen3-4B/config.json + +# 环境变量 +export HCCL_CONNECT_TIMEOUT=180 +export HCCL_IF_MTU=10240 +export VLLM_PLUGINS=fl +export VLLM_ASCEND_ENABLE_CONTEXT_PARALLEL=1 +export VLLM_WORKER_MULTIPROC_METHOD=spawn +export VLLM_ASCEND_USE_TORCHAIR=0 +export ASCEND_RT_VISIBLE_DEVICES=0,1 +export ASCEND_VISIBLE_DEVICES=0,1 +export VLLM_USE_MODELSCOPE=true +export TRITON_ALL_BLOCKS_PARALLEL=1 +export PYTORCH_NPU_ALLOC_CONF=max_split_size_mb:256 + +export VLLM_ASCEND_ENABLE_FUSED_MC2=1 # 启用融合算子,支持TND +export VLLM_ASCEND_ENABLE_FLASHCOMM=1 # 多卡并行时启用FlashComm +export TASK_QUEUE_ENABLE=2 # EP (Enqueued Processing) 模式 diff --git a/openseek/competition/LongContext-ICL-Annotation/src/llm_config.yaml b/openseek/competition/LongContext-ICL-Annotation/src/llm_config.yaml index 94e8a2cd..9d38e030 100644 --- a/openseek/competition/LongContext-ICL-Annotation/src/llm_config.yaml +++ b/openseek/competition/LongContext-ICL-Annotation/src/llm_config.yaml @@ -1,14 +1,27 @@ serve: -- serve_id: vllm_model - engine: vllm - engine_args: - model: ../Qwen3-4B - host: 0.0.0.0 - uvicorn_log_level: warning - port: 2026 - gpu_memory_utilization: 0.9 - trust_remote_code: true - no_enable_prefix_caching: true + - serve_id: vllm_model + engine: vllm + engine_args: + model: /root/.cache/modelscope/hub/models/Qwen/Qwen3-4B + host: 0.0.0.0 + served-model-name: Qwen/Qwen3-4B + uvicorn_log_level: warning + port: 2026 + trust_remote_code: true + gpu_memory_utilization: 0.9 + pipeline_parallel_size: 1 + tensor_parallel_size: 2 + block_size: 128 + max_num_seqs: 1 + max_model_len: 20480 + max_num_batched_tokens: 40960 + dtype: bfloat16 + enforce_eager: true + enable_prefix_caching: true + enable_chunked_prefill: true + disable_log_stats: true + async_scheduling: true + distributed_executor_backend: mp experiment: exp_name: qwen3_4b @@ -20,11 +33,11 @@ experiment: deploy: use_fs_serve: false envs: - CUDA_VISIBLE_DEVICES: 0 - CUDA_DEVICE_MAX_CONNECTIONS: 1 + ASCEND_RT_VISIBLE_DEVICES: 0,1 + ASCEND_RT_DEVICE_MAX_CONNECTIONS: 8 action: run hydra: run: - dir: ${experiment.exp_dir}/hydra \ No newline at end of file + dir: ${experiment.exp_dir}/hydra diff --git a/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s2_0.yaml b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s2_0.yaml new file mode 100644 index 00000000..a64d3591 --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s2_0.yaml @@ -0,0 +1,42 @@ +serve: + - serve_id: vllm_model + engine: vllm + engine_args: + model: /root/.cache/modelscope/hub/models/Qwen/Qwen3-4B + host: 0.0.0.0 + served-model-name: Qwen/Qwen3-4B + uvicorn_log_level: warning + port: 2026 + trust_remote_code: true + gpu_memory_utilization: 0.9 + pipeline_parallel_size: 1 + tensor_parallel_size: 1 + block_size: 128 + max_num_seqs: 1 + max_model_len: 20480 + max_num_batched_tokens: 40960 + dtype: bfloat16 + enforce_eager: true + enable_prefix_caching: true + enable_chunked_prefill: true + disable_log_stats: true + async_scheduling: true + +experiment: + exp_name: qwen3_4b + exp_dir: s2_0/${experiment.exp_name} + task: + type: serve + runner: + hostfile: null + deploy: + use_fs_serve: false + envs: + ASCEND_RT_VISIBLE_DEVICES: 0 + ASCEND_RT_DEVICE_MAX_CONNECTIONS: 8 + +action: run + +hydra: + run: + dir: ${experiment.exp_dir}/hydra diff --git a/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s2_1.yaml b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s2_1.yaml new file mode 100644 index 00000000..b32e43ce --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s2_1.yaml @@ -0,0 +1,42 @@ +serve: + - serve_id: vllm_model + engine: vllm + engine_args: + model: /root/.cache/modelscope/hub/models/Qwen/Qwen3-4B + host: 0.0.0.0 + served-model-name: Qwen/Qwen3-4B + uvicorn_log_level: warning + port: 2027 + trust_remote_code: true + gpu_memory_utilization: 0.9 + pipeline_parallel_size: 1 + tensor_parallel_size: 1 + block_size: 128 + max_num_seqs: 1 + max_model_len: 20480 + max_num_batched_tokens: 40960 + dtype: bfloat16 + enforce_eager: true + enable_prefix_caching: true + enable_chunked_prefill: true + disable_log_stats: true + async_scheduling: true + +experiment: + exp_name: qwen3_4b + exp_dir: s2_1/${experiment.exp_name} + task: + type: serve + runner: + hostfile: null + deploy: + use_fs_serve: false + envs: + ASCEND_RT_VISIBLE_DEVICES: 1 + ASCEND_RT_DEVICE_MAX_CONNECTIONS: 8 + +action: run + +hydra: + run: + dir: ${experiment.exp_dir}/hydra diff --git a/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_0.yaml b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_0.yaml new file mode 100644 index 00000000..fcf13b64 --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_0.yaml @@ -0,0 +1,42 @@ +serve: + - serve_id: vllm_model + engine: vllm + engine_args: + model: /root/.cache/modelscope/hub/models/Qwen/Qwen3-4B + host: 0.0.0.0 + served-model-name: Qwen/Qwen3-4B + uvicorn_log_level: warning + port: 2026 + trust_remote_code: true + gpu_memory_utilization: 0.4 + pipeline_parallel_size: 1 + tensor_parallel_size: 1 + block_size: 128 + max_num_seqs: 1 + max_model_len: 20480 + max_num_batched_tokens: 20480 + dtype: bfloat16 + enforce_eager: true + enable_prefix_caching: true + enable_chunked_prefill: true + disable_log_stats: true + async_scheduling: true + +experiment: + exp_name: qwen3_4b + exp_dir: s4_0/${experiment.exp_name} + task: + type: serve + runner: + hostfile: null + deploy: + use_fs_serve: false + envs: + ASCEND_RT_VISIBLE_DEVICES: 0 + ASCEND_RT_DEVICE_MAX_CONNECTIONS: 8 + +action: run + +hydra: + run: + dir: ${experiment.exp_dir}/hydra diff --git a/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_1.yaml b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_1.yaml new file mode 100644 index 00000000..62ba17d5 --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_1.yaml @@ -0,0 +1,42 @@ +serve: + - serve_id: vllm_model + engine: vllm + engine_args: + model: /root/.cache/modelscope/hub/models/Qwen/Qwen3-4B + host: 0.0.0.0 + served-model-name: Qwen/Qwen3-4B + uvicorn_log_level: warning + port: 2027 + trust_remote_code: true + gpu_memory_utilization: 0.4 + pipeline_parallel_size: 1 + tensor_parallel_size: 1 + block_size: 128 + max_num_seqs: 1 + max_model_len: 20480 + max_num_batched_tokens: 20480 + dtype: bfloat16 + enforce_eager: true + enable_prefix_caching: true + enable_chunked_prefill: true + disable_log_stats: true + async_scheduling: true + +experiment: + exp_name: qwen3_4b + exp_dir: s4_1/${experiment.exp_name} + task: + type: serve + runner: + hostfile: null + deploy: + use_fs_serve: false + envs: + ASCEND_RT_VISIBLE_DEVICES: 0 + ASCEND_RT_DEVICE_MAX_CONNECTIONS: 8 + +action: run + +hydra: + run: + dir: ${experiment.exp_dir}/hydra diff --git a/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_2.yaml b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_2.yaml new file mode 100644 index 00000000..63719d37 --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_2.yaml @@ -0,0 +1,42 @@ +serve: + - serve_id: vllm_model + engine: vllm + engine_args: + model: /root/.cache/modelscope/hub/models/Qwen/Qwen3-4B + host: 0.0.0.0 + served-model-name: Qwen/Qwen3-4B + uvicorn_log_level: warning + port: 2028 + trust_remote_code: true + gpu_memory_utilization: 0.4 + pipeline_parallel_size: 1 + tensor_parallel_size: 1 + block_size: 128 + max_num_seqs: 1 + max_model_len: 20480 + max_num_batched_tokens: 20480 + dtype: bfloat16 + enforce_eager: true + enable_prefix_caching: true + enable_chunked_prefill: true + disable_log_stats: true + async_scheduling: true + +experiment: + exp_name: qwen3_4b + exp_dir: s4_2/${experiment.exp_name} + task: + type: serve + runner: + hostfile: null + deploy: + use_fs_serve: false + envs: + ASCEND_RT_VISIBLE_DEVICES: 1 + ASCEND_RT_DEVICE_MAX_CONNECTIONS: 8 + +action: run + +hydra: + run: + dir: ${experiment.exp_dir}/hydra diff --git a/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_3.yaml b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_3.yaml new file mode 100644 index 00000000..f19a5132 --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/llm_config_s4_3.yaml @@ -0,0 +1,42 @@ +serve: + - serve_id: vllm_model + engine: vllm + engine_args: + model: /root/.cache/modelscope/hub/models/Qwen/Qwen3-4B + host: 0.0.0.0 + served-model-name: Qwen/Qwen3-4B + uvicorn_log_level: warning + port: 2029 + trust_remote_code: true + gpu_memory_utilization: 0.4 + pipeline_parallel_size: 1 + tensor_parallel_size: 1 + block_size: 128 + max_num_seqs: 1 + max_model_len: 20480 + max_num_batched_tokens: 20480 + dtype: bfloat16 + enforce_eager: true + enable_prefix_caching: true + enable_chunked_prefill: true + disable_log_stats: true + async_scheduling: true + +experiment: + exp_name: qwen3_4b + exp_dir: s4_3/${experiment.exp_name} + task: + type: serve + runner: + hostfile: null + deploy: + use_fs_serve: false + envs: + ASCEND_RT_VISIBLE_DEVICES: 1 + ASCEND_RT_DEVICE_MAX_CONNECTIONS: 8 + +action: run + +hydra: + run: + dir: ${experiment.exp_dir}/hydra diff --git a/openseek/competition/LongContext-ICL-Annotation/src/main.py b/openseek/competition/LongContext-ICL-Annotation/src/main.py index c2949795..17aefa48 100644 --- a/openseek/competition/LongContext-ICL-Annotation/src/main.py +++ b/openseek/competition/LongContext-ICL-Annotation/src/main.py @@ -1,92 +1,196 @@ -import json, os, argparse +import os +import re +import time +import json +import random +import argparse +random.seed(123) + from tqdm import tqdm, trange from transformers import AutoTokenizer -# from method import build_prompt, select_examples, annotate +from method import select_examples, select_operations, refer_get_operation_for_icl, refer_get_result_for_test +from const import CONST_TASK_FILES, CONST_TASK_OUTPUT_MAX_TOKENS_SIZES, CONST_LIMIT_PROMPT_MAX_TOKENS, CONST_LIMIT_OUTPUT_MAX_TOKENS, CONST_TASK_TEST_RETURN_DATA_TYPES -from method import build_prompt, select_examples -from method import annotate_nvidia as annotate # For Nvidia GPU -# from method import annotate_ascend as annotate # For Huawei Ascend +# 文件目录: {your-project-path}/OpenSeek/openseek/competition/LongContext-ICL-Annotation +LongContext_ICL_Annotation_DIR = os.path.dirname(os.path.dirname(__file__)) -TASK_FILES = { - 1: './data/openseek-1_closest_integers.json', - 2: './data/openseek-2_count_nouns_verbs.json', - 3: './data/openseek-3_collatz_conjecture.json', - 4: './data/openseek-4_conala_concat_strings.json', - 5: './data/openseek-5_semeval_2018_task1_tweet_sadness_detection.json', - 6: './data/openseek-6_mnli_same_genre_classification.json', - 7: './data/openseek-7_jeopardy_answer_generation_all.json', - 8: '../data/openseek-8_kernel_generation.json', -} +data_folder = 'data' +operation_folder = 'operations_tmp' +result_folder = 'outputs' -def parser_args(): - parser = argparse.ArgumentParser() - parser.add_argument('--task_id', type=int, required=True, - help='Task ID to evaluate, should be in [1, 7].') - parser.add_argument('--max_input_length', type=int, default=10_000, - help='Maximum input length for the model.') - parser.add_argument('--log_path_prefix', type=str, - default='../outputs/', - help='Prefix path to save the evaluation logs.') - parser.add_argument('--tokenizer_path', type=str, - default='/share/project/wuhaiming/spaces/data_agent/OpenSeek-main/openseek/competition/LongContext-ICL-Annotation/src/Qwen3-4B') - args = parser.parse_args() - return args - -def evaluate(task_id:int, - qwen_tokenizer:AutoTokenizer, - max_input_length:int=128_000, - log_path_prefix:str='./outputs/' - )->float: - assert task_id in [i for i in range(1, 9)],\ - f"task_id should be in [1, 8], but got {task_id}." - - task_file = TASK_FILES[task_id] +get_task_filepath = lambda task_id: os.path.join(LongContext_ICL_Annotation_DIR, data_folder, CONST_TASK_FILES[task_id]) +get_operation_filepath = lambda task_id: os.path.join(LongContext_ICL_Annotation_DIR, operation_folder, f'operation-{task_id}.json') +get_result_filepath = lambda task_id, version: os.path.join(LongContext_ICL_Annotation_DIR, result_folder, f'openseek-{task_id}-v{version}.jsonl') + +get_icl_progress_filepath = lambda task_id: os.path.join(LongContext_ICL_Annotation_DIR, f'icl_progress-{task_id}.log') +get_test_progress_filepath = lambda task_id: os.path.join(LongContext_ICL_Annotation_DIR, f'test_progress-{task_id}.log') + + +# 记录ICL提取执行进度 +def print_icl_progress(task_id, *args): + print(args) + +# 记录TEST执行进度 +def print_test_progress(task_id, *args): + print(args) + + +# 利用ICL提取特定问题的关键操作Operation,可以重复执行多轮,每一轮使用不同的ICL样本组合批次 +def processing( + task_id:int, + qwen_tokenizer:AutoTokenizer, + start=0, + stop=-1 +): + assert 1 <= task_id <= 8, f"task_id should be in [1,2,3,4,5,6,7,8], but got {task_id}." + assert stop < 0 or stop >= start, f"must be `stop < 0 or stop >= start`, stop error: {stop}" + + # 保存Operation + operation_file = get_operation_filepath(task_id) + print_icl_progress(task_id, 'operation_file', operation_file) + + task_operations = set() + operation_dir = os.path.dirname(operation_file) + os.makedirs(operation_dir, mode=0o755, exist_ok=True) + + task_file = get_task_filepath(task_id) + print_icl_progress(task_id, 'task_file', task_file) with open(task_file, 'r') as f: task_dict = json.load(f) - + + task_name = task_dict['task_name'] + task_description = task_dict['Definition'][0] + icl_examples = task_dict['examples'] + print_icl_progress(task_id, 'task_name', task_name) + print_icl_progress(task_id, 'icl_examples-size', len(icl_examples)) + + # 遍历所有示例样本,自动计算合适长度的批次 + start = start if start >= 0 else 0 + stop = stop if stop >= start else len(icl_examples) - 1 + while start <= stop: + + icl_batch_data, start_next = select_examples(icl_examples, qwen_tokenizer, start, stop, CONST_LIMIT_OUTPUT_MAX_TOKENS) + + print("\n") + print_icl_progress(task_id, 'icl_batch_data-strlen', len(icl_batch_data), "start", start, "batch_size", start_next - start, "task", task_id, task_name) + start = start_next + + operations, state = refer_get_operation_for_icl(task_description, icl_batch_data, CONST_LIMIT_OUTPUT_MAX_TOKENS) + + print("\n--------------------------------------------------------------------------------------------------------------------") + if operations == None: + print_icl_progress(task_id, f"The batch get operations is None, state: {state}", ) + else: + print_icl_progress(task_id, f"The batch get operations is Success, state: {state}", operations) + for v in operations: + task_operations.add(v) + + # 写入持久化文件 + operations_list = [v for v in task_operations] + print_icl_progress(task_id, 'operations_list-size', len(operations_list)) + + if len(operations_list) > 0: + with open(operation_file, 'w') as f: + json.dump(operations_list, f) + + print_icl_progress(task_id, "*"*50) + + +# 利用特定问题的Operation获取测试数据的推理结果 +def evaluate( + task_id:int, + qwen_tokenizer:AutoTokenizer, + start=0, + stop=-1 +): + assert 1 <= task_id <= 8, f"task_id should be in [1,2,3,4,5,6,7,8], but got {task_id}." + assert stop < 0 or stop >= start, f"must be `stop < 0 or stop >= start`, stop error: {stop}" + + # 加载Operation + operation_file = get_operation_filepath(task_id) + print_test_progress(task_id, 'operation_file', operation_file) + with open(operation_file, 'r') as f: + task_operations = json.load(f) + + # 无论有多少关键操作,都只提交推理一次测试,所以也只遍历一次关键操作,简单获取足量的关键操作即可 + random.shuffle(task_operations) + core_operations, _ = select_operations(task_operations, qwen_tokenizer, 0, len(task_operations) - 1, CONST_LIMIT_PROMPT_MAX_TOKENS) + print_test_progress(task_id, 'core_operations-strlen', len(core_operations)) + + # 加载任务文件 + task_file = get_task_filepath(task_id) + with open(task_file, 'r') as f: + task_dict = json.load(f) + task_name = task_dict['task_name'] task_description = task_dict['Definition'][0] - icl_examples = task_dict['examples'][:100] test_samples = task_dict['test_samples'] - + print_test_progress(task_id, 'test_samples-size', len(test_samples), "task", task_id, task_name) + + # 测试结果保存位置 version = 1 - output_file = f'{log_path_prefix}openseek-{task_id}-v{version}.jsonl' - output_path = os.path.dirname(output_file) - os.makedirs(output_path, exist_ok=True) - while os.path.exists(output_file): + result_file = get_result_filepath(task_id, version) + result_dir = os.path.dirname(result_file) + os.makedirs(result_dir, mode=0o755, exist_ok=True) + while os.path.exists(result_file): version += 1 - output_file = f'{log_path_prefix}openseek-{task_id}-v{version}.jsonl' - with open(output_file, 'w') as f: + result_file = get_result_filepath(task_id, version) + with open(result_file, 'w') as f: pass - - examples_str = None - for test_sample in tqdm(test_samples, desc=f'Evaluation on Task {task_id}: {task_name}'): - test_record = dict() - - test_sample_id = test_sample['id'] - test_record['test_sample_id'] = test_sample_id - - - text2annotate = test_sample['input'] - prompt = build_prompt(task_description, text2annotate) - if examples_str is None: - examples_str = select_examples(icl_examples, task_description, text2annotate) - input_prompt = prompt.replace("[[EXAMPLES]]\n\n", examples_str+'\n\n') - - # tokenized_input = qwen_tokenizer(input_prompt, return_tensors="pt") - # if tokenized_input['input_ids'].shape[1] > max_input_length: - # test_record['prediction'] = None - # else: - # prediction = annotate(input_prompt) - # test_record['prediction'] = prediction - prediction = annotate(input_prompt) - test_record['prediction'] = prediction - with open(output_file, 'a') as f: + + # 提交测试 + start = start if start >= 0 else 0 + stop = stop if stop >= start else len(test_samples) - 1 + for i in range(start, stop + 1): + print(f'Evaluation item={i} on Task {task_id}: {task_name}') + + test_sample_id = test_samples[i]['id'] + test_input = test_samples[i]['input'] + prediction, state = refer_get_result_for_test(task_description, test_input, core_operations, CONST_TASK_TEST_RETURN_DATA_TYPES[task_id], CONST_TASK_OUTPUT_MAX_TOKENS_SIZES[task_id]) + + print("\n--------------------------------------------------------------------------------------------------------------------") + print_test_progress(task_id, f"Answer state={state} item={i}:", prediction) + + # 保存测试结果 + test_record = {'test_sample_id': test_sample_id, "state": state, 'reps': 0, 'prediction': prediction} + with open(result_file, 'a') as f: f.write(json.dumps(test_record)+'\n') + print_test_progress(task_id, "*"*50) + + +# 命令参数解析 +def parser_args(): + parser = argparse.ArgumentParser() + + parser.add_argument('--task_id', type=int, required=True, + help='Task ID, one of [1,2,3,4,5,6,7,8]') + parser.add_argument('--task_step', type=int, required=True, + help='Task Step, one of [1,2], 1 for processing step, 2 for evaluate step') + + # 可定位某个ICL批次或某个TEST测试 + parser.add_argument('--start', type=int, default=0, help='Last Stop Task Row Index[0,len-1].') + parser.add_argument('--stop', type=int, default=-1, help='Next Stop Task Row Index[0,len-1].') + + # 分词器参数位置路径 + default_tokenizer_path = os.path.join(os.path.expanduser('~'), '.cache/modelscope/hub/models/Qwen/Qwen3-4B') + parser.add_argument('--tokenizer_path', type=str, default=default_tokenizer_path, help='Model path for serve.') + + return parser.parse_args() + + if __name__ == '__main__': args = parser_args() - qwen_tokenizer = AutoTokenizer.from_pretrained(args.tokenizer_path) - evaluate(args.task_id, qwen_tokenizer, args.max_input_length, args.log_path_prefix) \ No newline at end of file + + tokenizer_path = os.environ.get('SERVE_TOKENIZER_PATH', args.tokenizer_path) + qwen_tokenizer = AutoTokenizer.from_pretrained(tokenizer_path) + + # 首先提取Operation + if args.task_step == 1: + processing(args.task_id, qwen_tokenizer, args.start, args.stop) + + # 然后提交测试 + if args.task_step == 2: + evaluate(args.task_id, qwen_tokenizer, args.start, args.stop) diff --git a/openseek/competition/LongContext-ICL-Annotation/src/main_batch.py b/openseek/competition/LongContext-ICL-Annotation/src/main_batch.py new file mode 100644 index 00000000..3e9261bd --- /dev/null +++ b/openseek/competition/LongContext-ICL-Annotation/src/main_batch.py @@ -0,0 +1,379 @@ +import os +import re +import json +import time +import random +import shutil +import argparse +random.seed(123) + +from tqdm import tqdm, trange +from transformers import AutoTokenizer + +# 必须强制 DEBUG_PRINT_STREAM=0 防止在后台运行时实时打印模型的输出内容,会拖慢速度,而且实时打印是胡乱无序的 +os.environ['DEBUG_PRINT_STREAM'] = '0' + +from method import select_examples, select_operations, parse_result, batch_refer_get_operation_for_icl, batch_refer_get_result_for_test +from const import CONST_TASK_FILES, CONST_TASK_OUTPUT_MAX_TOKENS_SIZES, CONST_LIMIT_PROMPT_MAX_TOKENS, CONST_LIMIT_OUTPUT_MAX_TOKENS, CONST_TASK_CHECK_STRLEN_SIZES, CONST_TASK_TEST_RETURN_DATA_TYPES + + +# 提取轮次(若NPU卡较少,为了加速实验可选择默认N=1,若NPU卡较多 可以尝试多跑几遍) +try: + N = int(os.environ.get('ICL_REPEAT_N', 1)) +except ValueError: + N = 0 + +# 异步并发数(与服务器支持的批次并行相近即可, 协同服务端张量和数据并行计算, 可加速实验进度) +try: + req_concurrency_size = int(os.environ.get('REQ_CONCURRENCY_NUM', 2)) +except ValueError: + req_concurrency_size = 0 + +if N <=0: + raise Exception('ICL_REPEAT_N must be a integer and bigger than 0') +if req_concurrency_size <= 0: + raise Exception('REQ_CONCURRENCY_NUM must be a integer and bigger than 0') + + +# 文件目录: {your-project-path}/OpenSeek/openseek/competition/LongContext-ICL-Annotation +LongContext_ICL_Annotation_DIR = os.path.dirname(os.path.dirname(__file__)) + +data_folder = 'data' +operation_folder = 'operations_tmp' +result_folder = 'outputs' + +get_task_filepath = lambda task_id: os.path.join(LongContext_ICL_Annotation_DIR, data_folder, CONST_TASK_FILES[task_id]) +get_operation_filepath = lambda task_id: os.path.join(LongContext_ICL_Annotation_DIR, operation_folder, f'operation-{task_id}.json') +get_result_filepath = lambda task_id, version: os.path.join(LongContext_ICL_Annotation_DIR, result_folder, f'openseek-{task_id}-v{version}.jsonl') + +get_icl_progress_filepath = lambda task_id: os.path.join(LongContext_ICL_Annotation_DIR, f'icl_progress-{task_id}.log') +get_test_progress_filepath = lambda task_id: os.path.join(LongContext_ICL_Annotation_DIR, f'test_progress-{task_id}.log') + + +# 记录ICL提取执行进度 +def log_icl_progress(task_id, *args): + with open(get_icl_progress_filepath(task_id), 'a') as f: + f.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + ' :: ' + json.dumps(args)+'\n') + + +# 记录TEST执行进度 +def log_test_progress(task_id, *args): + with open(get_test_progress_filepath(task_id), 'a') as f: + f.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + ' :: ' + json.dumps(args)+'\n') + + +# 利用ICL提取特定问题的关键操作Operation,可以重复执行多轮,每一轮使用不同的ICL样本组合批次 +def processing(task_id:int, qwen_tokenizer:AutoTokenizer, start = 0, stop = -1): + assert 1 <= task_id <= 8, f"task_id should be in [1, 8], but got {task_id}." + assert stop < 0 or stop >= start, f"must be `stop < 0 or stop >= start`, stop error: {stop}" + + # 保存Operation + operation_file = get_operation_filepath(task_id) + log_icl_progress(task_id, 'operation_file', operation_file) + + task_operations = set() + + operation_dir = os.path.dirname(operation_file) + os.makedirs(operation_dir, mode=0o755, exist_ok=True) + + if os.path.exists(operation_file): + if N == 1: + log_icl_progress(task_id, 'not need repeat processing for N=1') + return + # 与上一轮的去重合并 + with open(operation_file, 'r') as f: + old_operations_list = json.load(f) + task_operations.update(old_operations_list) + + task_file = get_task_filepath(task_id) + log_icl_progress(task_id, 'task_file', task_file) + with open(task_file, 'r') as f: + task_dict = json.load(f) + + task_name = task_dict['task_name'] + task_description = task_dict['Definition'][0] + icl_examples = task_dict['examples'] + log_icl_progress(task_id, 'task_name', task_name) + log_icl_progress(task_id, 'icl_examples-size', len(icl_examples)) + + for i in range(N): + log_icl_progress(task_id, f"第 {i + 1} 轮次ICL遍历") + + # 在重复采样时,打乱ICL的顺序,每轮即可得到不同样本组合的批次,让样本表现为多样性 + if i > 0: + random.shuffle(icl_examples) + + # 遍历所有示例样本,自动计算合适长度的批次 + icl_batch_data_list = [] + start = start if start >= 0 else 0 + stop = stop if stop >= start else len(icl_examples) - 1 + while start <= stop: + icl_batch_data, start_next = select_examples(icl_examples, qwen_tokenizer, start, stop, CONST_LIMIT_PROMPT_MAX_TOKENS) + log_icl_progress(task_id, 'icl_batch_data-strlen:', len(icl_batch_data), f"N_i={i}", "start:", start, "icl_batch_size:", start_next - start, "task:", task_id, task_name) + start = start_next + + icl_batch_data_list.append(icl_batch_data) + if len(icl_batch_data_list) == req_concurrency_size or start >= stop: + operations_list = batch_refer_get_operation_for_icl(task_description, icl_batch_data_list, CONST_LIMIT_OUTPUT_MAX_TOKENS) + icl_batch_data_list = [] + + for operations, state in operations_list: + if operations == None: + log_icl_progress(task_id, f"The N_i={i} req_concurrency_size: {req_concurrency_size} get operations is None, state: {state}") + else: + log_icl_progress(task_id, f"The N_i={i} req_concurrency_size: {req_concurrency_size} get operations is Success, state: {state}", operations) + for v in operations: + task_operations.add(v) + + # 写入持久化文件 + operations_list = [v for v in task_operations] + log_icl_progress(task_id, 'operations_list-size', len(operations_list)) + + if len(operations_list) > 0: + with open(operation_file, 'w') as f: + json.dump(operations_list, f) + + log_icl_progress(task_id, "*"*50) + + +# 利用特定问题的Operation获取测试数据的推理结果 +def evaluate(task_id:int, qwen_tokenizer:AutoTokenizer, start = 0, stop = -1): + assert 1 <= task_id <= 8, f"task_id should be in [1, 8], but got {task_id}." + assert stop < 0 or stop >= start, f"must be `stop < 0 or stop >= start`, stop error: {stop}" + + # 加载Operation + operation_file = get_operation_filepath(task_id) + log_test_progress(task_id, 'operation_file', operation_file) + with open(operation_file, 'r') as f: + task_operations = json.load(f) + + # 无论有多少关键操作,都只提交推理一次测试,所以也只遍历一次关键操作,简单获取足量的关键操作即可 + random.shuffle(task_operations) + core_operations, _ = select_operations(task_operations, qwen_tokenizer, 0, len(task_operations) - 1, CONST_LIMIT_PROMPT_MAX_TOKENS) + log_test_progress(task_id, 'core_operations-strlen', len(core_operations)) + + # 加载任务文件 + task_file = get_task_filepath(task_id) + with open(task_file, 'r') as f: + task_dict = json.load(f) + + task_name = task_dict['task_name'] + task_description = task_dict['Definition'][0] + test_samples = task_dict['test_samples'] + log_test_progress(task_id, 'test_samples-size', len(test_samples), "task", task_id, task_name) + + # 测试结果保存位置 + version = 1 + result_file = get_result_filepath(task_id, version) + result_dir = os.path.dirname(result_file) + os.makedirs(result_dir, mode=0o755, exist_ok=True) + while os.path.exists(result_file): + version += 1 + result_file = get_result_filepath(task_id, version) + with open(result_file, 'w') as f: + pass + + # 提交测试 + start = start if start >= 0 else 0 + stop = stop if stop >= start else len(test_samples) - 1 + for i in range(start, stop + 1, req_concurrency_size): + log_test_progress(task_id, f'Evaluation items({i}:{i+req_concurrency_size}) on Task {task_id}: {task_name}') + + test_sample_id_list = [] + test_input_list = [] + for test_sample in test_samples[i: min(i + req_concurrency_size, stop + 1)]: + test_sample_id_list.append(test_sample['id']) + test_input_list.append(test_sample['input']) + + prediction_list = batch_refer_get_result_for_test(task_description, test_input_list, core_operations, CONST_TASK_TEST_RETURN_DATA_TYPES[task_id], CONST_TASK_OUTPUT_MAX_TOKENS_SIZES[task_id]) + + j = 0 + for test_sample_id, (prediction, state) in zip(test_sample_id_list, prediction_list): + log_test_progress(task_id, f"Answer state={state} item={i+j}:", prediction) + j += 1 + + # 保存测试结果 + test_record = {'test_sample_id': test_sample_id, 'state': state, 'reps': 0, 'prediction': prediction} + with open(result_file, 'a') as f: + f.write(json.dumps(test_record)+'\n') + + log_test_progress(task_id, "*"*50) + + +# 自动检查异常数据:可能是服务端为正常返回,也可能是KV缓存,也可能是模型不稳定 +# 像这样的异常数据,可用通过重新请求服务,最终得到正常输出 +def check_evaluate_retry(task_id:int, qwen_tokenizer:AutoTokenizer, reps = 0): + assert 1 <= task_id <= 8, f"task_id should be in [1, 8], but got {task_id}." + + # 测试结果保存位置 + version = 1 + result_file = get_result_filepath(task_id, version) + if not os.path.exists(result_file): + # 没有结果文件,无需检查 + print(f'task_id={task_id}暂无对应结果文件,无需检查') + return + + min_len, max_len = CONST_TASK_CHECK_STRLEN_SIZES[task_id]['min'], CONST_TASK_CHECK_STRLEN_SIZES[task_id]['max'] + checks = {} + outputs = [] + with open(result_file, 'r') as file: + # 逐行读取并解析JSON + i = 0 + for line in file: + # 解析每行的JSON数据 + data = json.loads(line) + outputs.append(data) + # 检查是否满足特定条件 + data_len = len(data['prediction']) if data['prediction'] != None else 0 + if data['state'] == False or data_len < min_len or data_len > max_len: + checks[data['test_sample_id']] = i + # next + i += 1 + + if len(checks.values()) == 0: + # 没有异常数据,无需检查 + print(f'task_id={task_id}没有异常数据,无需检查') + return + + # 备份 + backup_result_file = result_file + '_backup' + shutil.copy(result_file, backup_result_file) + + # 加载Operation + operation_file = get_operation_filepath(task_id) + log_test_progress(task_id, 'operation_file', operation_file) + with open(operation_file, 'r') as f: + task_operations = json.load(f) + + # 无论有多少关键操作,都只提交推理一次测试,所以也只遍历一次关键操作,简单获取足量的关键操作即可 + random.shuffle(task_operations) + core_operations, _ = select_operations(task_operations, qwen_tokenizer, 0, len(task_operations) - 1, CONST_LIMIT_PROMPT_MAX_TOKENS) + log_test_progress(task_id, 'core_operations-strlen', len(core_operations)) + + # 加载任务文件 + task_file = get_task_filepath(task_id) + with open(task_file, 'r') as f: + task_dict = json.load(f) + + task_name = task_dict['task_name'] + task_description = task_dict['Definition'][0] + test_samples = task_dict['test_samples'] + log_test_progress(task_id, 'test_samples-size', len(test_samples), "task", task_id, task_name) + + check_ids = checks.keys() + indexs = [ i for i, item in enumerate(test_samples) if item['id'] in check_ids ] + for k in range(0, len(indexs), req_concurrency_size): + sample_idxs = indexs[k : k + req_concurrency_size] + + test_sample_id_list = [] + test_input_list = [] + for i in sample_idxs: + test_sample_id_list.append(test_samples[i]['id']) + test_input_list.append(test_samples[i]['input']) + + log_test_progress(task_id, f"Retry items={sample_idxs}") + prediction_list = batch_refer_get_result_for_test(task_description, test_input_list, core_operations, CONST_TASK_TEST_RETURN_DATA_TYPES[task_id], CONST_TASK_OUTPUT_MAX_TOKENS_SIZES[task_id]) + + j = 0 + for test_sample_id, (prediction, state) in zip(test_sample_id_list, prediction_list): + raw = prediction + if state == False: + prediction, _ = parse_result(prediction, CONST_TASK_TEST_RETURN_DATA_TYPES[task_id]) + + log_test_progress(task_id, f"Correct state={state} item={sample_idxs[j]}:", prediction) + j += 1 + + # 保存测试结果 + outputs[checks[test_sample_id]] = {'test_sample_id': test_sample_id, 'state': state, 'reps': reps, 'prediction': prediction} + if raw != prediction: + outputs[checks[test_sample_id]]['raw'] = raw + + new_result_file = get_result_filepath(task_id, version + 1) + with open(new_result_file, 'w') as f: + for data in outputs: + f.write(json.dumps(data) + '\n') + + # 覆盖旧文件 + os.replace(new_result_file, result_file) + log_test_progress(task_id, "*"*50) + + +# 命令参数解析 +def parser_args(): + parser = argparse.ArgumentParser() + + parser.add_argument( + '--task_id', + type=str, + required=True, + help='Task ID, examples: task_id=1 or task_id=[1,2] or task_id=0, all task if task_id=0 or task_id is empty, one task if task_id>=1 and task_id<=8, multi task if task_id is list' + ) + parser.add_argument( + '--task_step', + type=str, + required=True, + help='Task Step, examples: task_step=1 or task_step=[1,2] or task_step=0, all if task_step=0 or task_step is empty, processing if task_step=1, evaluate if task_step=2, check_evaluate_retry if task_step=3' + ) + + # 可定位某个ICL批次或某个TEST测试 + parser.add_argument('--start', type=int, default=0, help='Last Stop Task Row Index[0,len-1].') + parser.add_argument('--stop', type=int, default=-1, help='Next Stop Task Row Index[0,len-1].') + + # 分词器参数位置路径 + default_tokenizer_path = os.path.join(os.path.expanduser('~'), '.cache/modelscope/hub/models/Qwen/Qwen3-4B') + parser.add_argument('--tokenizer_path', type=str, default=default_tokenizer_path, help='Model path for serve.') + + return parser.parse_args() + + +def parse_args_task_id_or_step(arg_value: str, default_list: list): + arg_value = arg_value.strip() + if not arg_value or arg_value == '0': + return default_list + + cleaned = arg_value.lstrip('[').rstrip(']') + try: + return [int(x.strip()) for x in cleaned.split(',') if x.strip()] + except ValueError: + raise ValueError(f"Invalid list of integers: {arg_value}") + + +if __name__ == '__main__': + args = parser_args() + + task_step_list = parse_args_task_id_or_step(args.task_step, [1, 2, 3]) + for task_step in task_step_list: + if not task_step in [1,2,3]: + raise Exception(f'Task Step error: {task_step_list}') + + task_id_list = parse_args_task_id_or_step(args.task_id, [1, 2, 3, 4, 5, 6, 7, 8]) + for task_id in task_id_list: + if not task_id in [1,2,3,4,5,6,7,8]: + raise Exception(f'Task ID error: {task_id_list}') + + # tokenizer + tokenizer_path = os.environ.get('SERVE_TOKENIZER_PATH', args.tokenizer_path) + qwen_tokenizer = AutoTokenizer.from_pretrained(tokenizer_path) + + # 串行处理任务 + for task_id in task_id_list: + + # 首先提取Operation + if 1 in task_step_list: + processing(task_id, qwen_tokenizer, args.start, args.stop) + args.start = 0 + args.stop = -1 + + # 然后提交测试 + if 2 in task_step_list: + evaluate(task_id, qwen_tokenizer, args.start, args.stop) + args.start = 0 + args.stop = -1 + + # 最后检查Result异常数据:可能是GPU负荷太高、GPU高温降频等无法正常完成,也可能是KV缓存干扰,也可能是模型输出不稳定,可能是网络问题等等 + if 3 in task_step_list: + # 检查3遍 + for i in range(3): + reps = i + 1 + check_evaluate_retry(task_id, qwen_tokenizer, reps) + time.sleep(30 * reps) diff --git a/openseek/competition/LongContext-ICL-Annotation/src/method.py b/openseek/competition/LongContext-ICL-Annotation/src/method.py index 386daf22..e3f07a5d 100644 --- a/openseek/competition/LongContext-ICL-Annotation/src/method.py +++ b/openseek/competition/LongContext-ICL-Annotation/src/method.py @@ -1,277 +1,357 @@ - import re + from collections import Counter from transformers import AutoTokenizer -""" Here is an example of implementation of Long-Context Data Annotation. """ +from api_client import ChatClient +from const import CONST_LIMIT_OUTPUT_MAX_TOKENS -def build_prompt____(task_description: str, text2annotate: str) -> str: - """ - Build a high-precision English prompt for long-context data annotation (optimized for Qwen3-4B). - Core requirement: Final answer MUST be wrapped in