diff --git a/.env b/.env new file mode 100644 index 00000000..868fef40 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +HY3_BASE_URL=https://tokenhub.tencentmaas.com/v1 +HY3_API_KEY=sk-s4wm9zZmjDJSJ1MPUFyFJ2YNaDBF2gvXLIdNWNMKdyqbJ0hp +HY3_MODEL=hy3 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 56137961..8831fba3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ Thumbs.db __pycache__/ *.pyc +venv/ +.env \ No newline at end of file diff --git a/README.md b/README.md index 447a1415..d118cf0f 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ - [vLLM](#vllm) - [SGLang](#sglang) - [Finetuning](#finetuning) +- [RL Post-training](#rl-post-training) - [Quantization](#quantization) - [License](#license) - [Contact Us](#contact-us) @@ -205,6 +206,10 @@ python3 -m sglang.launch_server \ Hy3 provides a complete model finetuning pipeline. For detailed documentation, please refer to: [Finetuning Guide](./finetune/README.md) +## RL Post-training + +Hy3 supports GRPO reinforcement learning training with [verl](https://github.com/volcengine/verl), training on Megatron-LM (model conversion via NVIDIA Megatron-Bridge) with vLLM rollout. For detailed documentation, please refer to: [RL Training Guide](./rl/README.md) + ## Quantization We provide [AngelSlim](https://github.com/tencent/AngelSlim), a more accessible, comprehensive, and efficient toolkit for large model compression. AngelSlim supports a comprehensive suite of compression tools for large-scale multimodal models, including common quantization algorithms, low-bit quantization, and speculative sampling. diff --git a/README_CN.md b/README_CN.md index dd7b73c0..f95b1f32 100644 --- a/README_CN.md +++ b/README_CN.md @@ -41,6 +41,7 @@ - [vLLM](#使用-vllm-推理) - [SGLang](#使用-sglang-推理) - [模型微调](#模型微调) +- [强化学习训练](#强化学习训练) - [量化工具](#量化工具) - [许可证](#许可证) - [联系我们](#联系我们) @@ -205,6 +206,10 @@ python3 -m sglang.launch_server \ Hy3 提供了完整的模型微调流程,详细的微调文档请参考:[模型微调指南](./finetune/README_CN.md) +## 强化学习训练 + +Hy3 支持基于 [verl](https://github.com/volcengine/verl) 的 GRPO 强化学习训练,训练侧使用 Megatron-LM(通过 NVIDIA Megatron-Bridge 完成模型转换),rollout 侧使用 vLLM。详细文档请参考:[强化学习训练指南](./rl/README_CN.md) + ## 量化工具 我们提供了 [AngelSlim](https://github.com/tencent/AngelSlim)——一套易用、全面、高效的大模型压缩工具包,涵盖常用量化算法、低比特量化和投机采样等能力。 diff --git a/assets/rl-training.png b/assets/rl-training.png new file mode 100644 index 00000000..2da8d819 Binary files /dev/null and b/assets/rl-training.png differ diff --git a/docs/01_basic_chat.md b/docs/01_basic_chat.md new file mode 100644 index 00000000..ec8e0537 --- /dev/null +++ b/docs/01_basic_chat.md @@ -0,0 +1,46 @@ +\# 示例 1:基础聊天 + + + +\## 功能说明 + +演示 Hy3 的单轮对话和多轮对话能力。 + + + +\## 完整请求 + +单轮对话使用 `messages` 传入单条用户消息;多轮对话通过追加 `assistant` 和 `user` 消息实现上下文传递。 + + + +\## 响应解析 + +\- `content`: 模型生成的回复 + +\- `finish\_reason`: stop(正常结束)/ length(达到长度限制) + +\- `usage`: Token 用量统计 + + + +\## 示例输出 + +标题: 单轮对话 + +回复: RESTful API 是一种基于 HTTP 协议,以资源为中心并通过标准方法(如 GET、POST、PUT、DELETE)进行无状态交互的 Web 接口设计风格。 + +结束原因: stop + +Token 用量: total\_tokens=60 + + + +标题: 多轮 - 第1轮 + +回复: 在 Python 中读取 CSV 文件有多种方式... + +结束原因: length + +Token 用量: total\_tokens=331 + diff --git a/docs/02_streaming.mdy b/docs/02_streaming.mdy new file mode 100644 index 00000000..6ccca119 --- /dev/null +++ b/docs/02_streaming.mdy @@ -0,0 +1,69 @@ +# 示例 2:流式请求 + +## 功能说明 +流式请求允许模型在生成完整响应之前,逐字、逐块地将内容返回给客户端。这能显著提升用户体验,让用户无需等待全部生成完毕即可开始阅读。 + +## 适用场景 +- 聊天机器人(实时对话) +- 内容生成(写作辅助) +- 任何需要快速反馈的 AI 应用 + +## 完整请求 + +```python +stream = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "user", "content": "用一句话解释什么是 Docker,然后列举 3 个常用命令。"} + ], + temperature=0.7, + max_tokens=200, + stream=True, # 开启流式 + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} +) + +for chunk in stream: + if chunk.choices[0].delta.content: + print(chunk.choices[0].delta.content, end="", flush=True) +``` + +## 响应解析 + +| 字段 | 说明 | +|------|------| +| `chunk.choices[0].delta.content` | 当前流式块中的文本片段 | +| `chunk.choices[0].finish_reason` | 在最后一个块中返回,表示结束原因 | +| `chunk.id` | 当前块 ID(同一请求的所有块共享相同 ID) | +| `chunk.model` | 使用的模型名称 | + +**关键指标:** +- **首 Token 时延**:从请求发出到收到第一个内容块的时间,衡量"用户看到第一个字需要等多久" +- **总耗时**:收到完整响应所需的总时间 +- **总块数**:响应被切分成的数据块数量 + +## 示例输出 + +```text +Docker 是一个将应用及其依赖打包成轻量级、可移植容器的开源平台,实现"一次构建,到处运行"。 + +常用命令: +1. `docker run`:创建并启动容器 +2. `docker ps`:查看运行中的容器 +3. `docker build`:根据 Dockerfile 构建镜像 + +📊 统计信息: + - 总块数: 36 + - 首 Token 时延: 1.72 秒 + - 总耗时: 2.84 秒 + - 输出长度: 148 字符 +``` + +## 与非流式的对比 + +| 对比项 | 流式 | 非流式 | +|--------|------|--------| +| 首 Token 时延 | 短(1-3秒) | 长(需等完整生成) | +| 用户感知等待 | 几乎即时 | 需等待数秒 | +| 总耗时 | 略长 | 略短 | +| 代码复杂度 | 稍复杂 | 简单 | +| 推荐场景 | 实时交互 | 后台批处理 | \ No newline at end of file diff --git a/docs/03_compare.md b/docs/03_compare.md new file mode 100644 index 00000000..df7dad4b --- /dev/null +++ b/docs/03_compare.md @@ -0,0 +1,188 @@ +\# 示例 3:流式 vs 非流式对比 + + + +\## 功能说明 + +通过同一个问题,分别使用流式和非流式两种方式请求,对比首 Token 时延、总耗时、输出长度等关键指标,帮助开发者根据场景选择合适的方式。 + + + +\## 适用场景 + +\- 接口选型评估 + +\- 性能测试 + +\- 用户体验优化决策 + + + +\## 完整请求 + + + +\### 非流式请求 + + + +```python + +response = client.chat.completions.create( + + model=MODEL, + + messages=\[{"role": "user", "content": prompt}], + + temperature=0.7, + + max\_tokens=150, + + extra\_body={"chat\_template\_kwargs": {"reasoning\_effort": "no\_think"}} + +) + +\# 等待完整响应后一次性返回 + +``` + + + +\### 流式请求 + + + +```python + +stream = client.chat.completions.create( + + model=MODEL, + + messages=\[{"role": "user", "content": prompt}], + + temperature=0.7, + + max\_tokens=150, + + stream=True, + + extra\_body={"chat\_template\_kwargs": {"reasoning\_effort": "no\_think"}} + +) + + + +first\_token\_time = None + +for chunk in stream: + + if first\_token\_time is None and chunk.choices\[0].delta.content: + + first\_token\_time = time.time() + + if chunk.choices\[0].delta.content: + + content += chunk.choices\[0].delta.content + +``` + + + +\## 响应解析 + + + +\### 非流式响应 + +| 字段 | 说明 | + +|------|------| + +| `response.choices\[0].message.content` | 完整回复内容 | + +| `response.usage.total\_tokens` | 总 Token 消耗量 | + +| `elapsed\_time` | 从请求到收到完整响应的总耗时 | + + + +\### 流式响应 + +| 字段 | 说明 | + +|------|------| + +| `first\_token\_time` | 收到第一个内容块的时间 | + +| `first\_token\_latency` | 首 Token 时延(= first\_token\_time - start\_time)| + +| `elapsed\_time` | 收到完整响应的总耗时 | + +| `chunk\_count` | 响应被拆分的块数 | + + + +\## 示例输出 + + + +```text + + 测试 Prompt: 什么是微服务架构?请用 100 字左右概括其主要特点。 + + + + 非流式模式: + + 完成 (3.12秒) + + 内容: 微服务架构是将单一应用拆分为多个小型、独立服务的设计模式... + + Token: 91 + + + + 流式模式: + + 完成 (3.76秒) + + 首 Token 时延: 2.40秒 + + 内容: 微服务架构是将单一应用拆分为多个小型、独立服务的设计模式... + + 长度: 101 字符 + + + + 对比结果: + + 非流式总耗时: 3.12秒 + + 流式总耗时: 3.76秒 + + 流式首 Token 时延: 2.40秒 + +``` + + + +\## 结论与建议 + + + +| 场景 | 推荐方式 | 原因 | + +|------|----------|------| + +| 实时对话/聊天 | 流式 | 用户能立即看到回复,体验好 | + +| 后台批量处理 | 非流式 | 代码简单,总耗时略短 | + +| 长文本生成 | 流式 | 用户可提前阅读,等待感弱 | + +| API 网关/代理 | 非流式 | 处理逻辑简单,无需流式协议 | + + + +> \*\*核心原则\*\*:流式优化的是"首屏体验",非流式优化的是"总完成时间"。 + diff --git a/docs/04_tool_calling.md b/docs/04_tool_calling.md new file mode 100644 index 00000000..1ef9bf77 --- /dev/null +++ b/docs/04_tool_calling.md @@ -0,0 +1,332 @@ +\# 示例 4:工具调用 + + + +\## 功能说明 + +演示如何让 Hy3 调用外部工具(函数)来获取实时信息或执行计算。模型会自主判断是否需要调用工具,并生成符合工具格式的调用请求。 + + + +\## 适用场景 + +\- 获取实时信息(天气、时间、股票等) + +\- 执行数学计算 + +\- 数据库查询 + +\- 调用第三方 API + +\- 任何需要外部数据或计算的任务 + + + +\## 完整请求 + + + +\### 1. 定义工具 + + + +```python + +tools = \[ + + { + + "type": "function", + + "function": { + + "name": "get\_current\_time", + + "description": "获取当前日期和时间", + + "parameters": { + + "type": "object", + + "properties": { + + "format": { + + "type": "string", + + "enum": \["full", "date", "time"], + + "description": "时间格式" + + } + + } + + } + + } + + }, + + { + + "type": "function", + + "function": { + + "name": "calculate", + + "description": "执行数学运算", + + "parameters": { + + "type": "object", + + "properties": { + + "operation": { + + "type": "string", + + "enum": \["add", "subtract", "multiply", "divide"], + + "description": "运算类型" + + }, + + "a": {"type": "number"}, + + "b": {"type": "number"} + + }, + + "required": \["operation", "a", "b"] + + } + + } + + } + +] + +``` + + + +\### 2. 发起带工具的请求 + + + +```python + +response = client.chat.completions.create( + + model=MODEL, + + messages=\[{"role": "user", "content": "现在几点了?"}], + + tools=tools, + + tool\_choice="auto", # 让模型自主决定是否调用工具 + + temperature=0.7, + + max\_tokens=200, + + extra\_body={"chat\_template\_kwargs": {"reasoning\_effort": "no\_think"}} + +) + +``` + + + +\### 3. 处理工具调用(多轮循环) + + + +```python + +if response.choices\[0].message.tool\_calls: + + # 获取工具调用信息 + + tool\_call = response.choices\[0].message.tool\_calls\[0] + + func\_name = tool\_call.function.name + + args = json.loads(tool\_call.function.arguments) + + + + # 执行对应的工具 + + if func\_name == "get\_current\_time": + + result = get\_current\_time(args.get("format", "full")) + + elif func\_name == "calculate": + + result = calculate(args\["operation"], args\["a"], args\["b"]) + + + + # 将工具结果返回给模型 + + messages.append(response.choices\[0].message) + + messages.append({ + + "role": "tool", + + "tool\_call\_id": tool\_call.id, + + "content": result + + }) + + + + # 让模型基于工具结果生成最终回复 + + final = client.chat.completions.create( + + model=MODEL, + + messages=messages, + + temperature=0.7, + + max\_tokens=200, + + extra\_body={"chat\_template\_kwargs": {"reasoning\_effort": "no\_think"}} + + ) + +``` + + + +\## 响应解析 + + + +\### 模型响应中的 tool\_calls + + + +| 字段 | 说明 | + +|------|------| + +| `tool\_calls\[0].function.name` | 要调用的工具名称 | + +| `tool\_calls\[0].function.arguments` | 调用参数(JSON 字符串) | + +| `tool\_calls\[0].id` | 工具调用唯一标识(用于匹配结果) | + + + +\### 工具结果回传 + + + +| 字段 | 说明 | + +|------|------| + +| `role: "tool"` | 标识为工具返回结果 | + +| `tool\_call\_id` | 对应工具调用的 ID | + +| `content` | 工具执行结果(字符串) | + + + +\## 工具调用流程图 + + + +``` + +用户提问 → 模型判断是否需要工具 + + ↓ + + 需要调用工具 + + ↓ + + 模型返回 tool\_calls(工具名称 + 参数) + + ↓ + + 开发者执行对应的工具函数 + + ↓ + + 将工具结果以 "tool" 角色回传给模型 + + ↓ + + 模型基于工具结果生成最终回复 + + ↓ + + 返回用户 + +``` + + + +\## 示例输出 + + + +```text + + 示例 1: 查询当前时间 + +\--- + +用户: 现在几点了? + +调用工具: get\_current\_time({'format': 'time'}) + +工具返回: 20:39:59 + +AI 回复: 现在是 \*\*20:39\*\*(晚上 8 点 39 分)。 + + + + 示例 2: 数学计算 + +\--- + +用户: 25 乘以 37 等于多少? + +调用工具: calculate({'operation': 'multiply', 'a': 25, 'b': 37}) + +计算结果: 925 + +AI 回复: 25 × 37 = \*\*925\*\*。 + +``` + + + +\## 关键要点 + + + +1\. \*\*工具定义\*\*:使用 OpenAI 兼容的 function 格式 + +2\. \*\*自动决策\*\*:设置 `tool\_choice="auto"` 让模型自主判断 + +3\. \*\*多轮循环\*\*:模型可能连续调用多个工具 + +4\. \*\*结果回传\*\*:必须以 `"role": "tool"` 格式返回 + +5\. \*\*必填参数\*\*:在 `required` 数组中声明 + diff --git a/docs/05_reasoning.md b/docs/05_reasoning.md new file mode 100644 index 00000000..3d4dab4f --- /dev/null +++ b/docs/05_reasoning.md @@ -0,0 +1,156 @@ +\# 示例 5:推理模式对比 + + + +\## 功能说明 + +演示 Hy3 的三种推理模式(`no\_think`、`low`、`high`)的差异,包括 Token 消耗、是否输出思考过程、回答质量等方面的对比。 + + + +\## 适用场景 + +\- 需要模型展示推理步骤的教育/教学场景 + +\- 对 Token 成本敏感的应用 + +\- 需要调试模型思维链的场景 + + + +\## 完整请求 + + + +```python + +response = client.chat.completions.create( + + model=MODEL, + + messages=\[{"role": "user", "content": prompt}], + + temperature=0.7, + + max\_tokens=400, + + extra\_body={ + + "chat\_template\_kwargs": { + + "reasoning\_effort": mode # "no\_think" | "low" | "high" + + } + + } + +) + +``` + + + +\## 推理模式说明 + + + +| 模式 | 说明 | Token 消耗 | 思考过程 | + +|------|------|-----------|---------| + +| `no\_think` | 不输出推理过程,直接给出答案 | 最低 | 无 | + +| `low` | 低强度推理,少量思考痕迹 | 中等 | 可能有 | + +| `high` | 高强度推理,完整展示思考链 | 最高 | 有 | + + + +\## 响应解析 + + + +| 字段 | 说明 | + +|------|------| + +| `message.content` | 模型的最终回答 | + +| `message.reasoning\_content` 或 `message.reasoning` | 思考过程(如存在) | + +| `usage.total\_tokens` | 总 Token 消耗量 | + + + +\## 示例输出 + + + +```text + +测试问题: 一个班级有 40 个学生,男生比女生多 6 人,问男生和女生各有多少人? + + + + 推理模式: no\_think + + Token 用量: 238 + + 思考过程: 无(直接输出) + + 回答预览: 设女生人数为 x,则男生为 x+6... + + + + 推理模式: low + + Token 用量: 307 + + 思考过程: 无(直接输出) + + 回答预览: 我们一步步来解这个题... + + + + 推理模式: high + + Token 用量: 303 + + 思考过程: 有 + + 回答预览: 设女生人数为 x,则男生为 x+6... + +``` + + + +\## 选择建议 + + + +| 场景 | 推荐模式 | 原因 | + +|------|---------|------| + +| 实时对话/聊天 | `no\_think` | 响应快,成本低 | + +| 数学/逻辑问题 | `low` 或 `high` | 需要展示推导过程 | + +| 教育/教学场景 | `high` | 展示完整思维链 | + +| 成本敏感应用 | `no\_think` | Token 消耗最少 | + +| 调试/分析 | `high` | 便于理解模型决策 | + + + +\## 注意事项 + + + +1\. `reasoning\_content` 字段的具体名称可能因模型版本而异(`reasoning\_content` 或 `reasoning`) + +2\. 并非所有模型版本都支持显示推理内容 + +3\. 推理内容不包含在 `content` 中,需要通过额外字段获取 + diff --git a/docs/06_error_handling.md b/docs/06_error_handling.md new file mode 100644 index 00000000..89da5bea --- /dev/null +++ b/docs/06_error_handling.md @@ -0,0 +1,242 @@ +\# 示例 6:错误处理与重试 + + + +\## 功能说明 + +演示如何优雅地处理 API 调用中可能出现的各种错误,包括超时、限流、网络错误等,并实现指数退避重试机制。 + + + +\## 适用场景 + +\- 生产环境中的稳定调用 + +\- 高并发场景下的容错 + +\- 网络不稳定的环境 + + + +\## 常见错误类型 + + + +| 错误类型 | 说明 | 是否可重试 | + +|---------|------|-----------| + +| `RateLimitError` | 请求频率超限 | 是(等待后重试) | + +| `APITimeoutError` | 请求超时 | 是 | + +| `APIConnectionError` | 网络连接失败 | 是 | + +| `APIError` | 其他 API 错误 | 否(需排查) | + +| `AuthenticationError` | API Key 无效 | 否(需修正) | + + + +\## 完整请求(带重试装饰器) + + + +```python + +def call\_with\_retry(max\_retries=3): + + def decorator(func): + + def wrapper(\*args, \*\*kwargs): + + for i in range(max\_retries): + + try: + + return func(\*args, \*\*kwargs) + + except Exception as e: + + print(f" 第 {i+1} 次尝试失败: {type(e).\_\_name\_\_}") + + if i == max\_retries - 1: + + raise # 最后一次失败,向上抛出 + + wait = 2 \*\* i # 指数退避:1, 2, 4, 8... + + print(f"⏳ 等待 {wait} 秒后重试...") + + time.sleep(wait) + + return None + + return wrapper + + return decorator + + + +\# 使用示例 + +@call\_with\_retry(max\_retries=3) + +def safe\_call(): + + return client.chat.completions.create( + + model=MODEL, + + messages=\[{"role": "user", "content": "你好"}], + + max\_tokens=80, + + extra\_body={"chat\_template\_kwargs": {"reasoning\_effort": "no\_think"}} + + ) + + + +try: + + response = safe\_call() + +except Exception as e: + + print(f" 最终失败: {e}") + +``` + + + +\## 指数退避策略 + + + +| 重试次数 | 等待时间 | 累计等待 | + +|---------|---------|---------| + +| 第1次失败 | 1 秒 | 1 秒 | + +| 第2次失败 | 2 秒 | 3 秒 | + +| 第3次失败 | 4 秒 | 7 秒 | + +| 第n次失败 | 2^(n-1) 秒 | - | + + + +\## 响应解析 + + + +\### 成功响应 + +```python + +\# 正常返回 ChatCompletion 对象 + +response = client.chat.completions.create(...) + +print(response.choices\[0].message.content) + +``` + + + +\### 错误响应 + +```python + +try: + + response = client.chat.completions.create(...) + +except RateLimitError as e: + + # HTTP 429 - 请求过多 + + print(f"限流: {e}") + +except APITimeoutError as e: + + # 超时 + + print(f"超时: {e}") + +except APIError as e: + + # 其他 API 错误 + + print(f"API 错误: {e}") + +except Exception as e: + + # 未知错误 + + print(f"未知错误: {e}") + +``` + + + +\## 示例输出 + + + +```text + + 正常调用(带重试保护): + + 成功! 回复: 你好!我是混元,是由腾讯开发的大模型... + + + + 错误处理最佳实践: + + + +1\. 使用 try-except 捕获异常 + +2\. 对限流/超时使用指数退避重试 + +3\. 设置合理的超时时间(如 30-60 秒) + +4\. 记录错误日志便于调试 + +5\. 设置最大重试次数避免无限循环(如 3-5 次) + +``` + + + +\## 最佳实践总结 + + + +\### 推荐做法 + +\- 对临时性错误(限流、超时)实施重试 + +\- 使用指数退避避免加重服务器负担 + +\- 设置最大重试次数(3-5次) + +\- 记录详细的错误日志 + +\- 区分可重试和不可重试错误 + + + +\### 避免做法 + +\- 无限循环重试 + +\- 立即重试(无等待) + +\- 忽略所有错误 + +\- 重试不可恢复的错误(如认证失败) + diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 00000000..5cc50cff --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,530 @@ +\# Hy3 API Quickstart + + + +> 5 分钟跑通第一次调用,半小时上手主要能力 + + + + + +\## 目录 + + + +\- \[基础信息](#基础信息) + +\- \[快速开始](#快速开始) + +\- \[参数说明](#参数说明) + +\- \[示例集合](#示例集合) + +\- \[常见报错排查](#常见报错排查) + + + + + +\## 基础信息 + + + +\### 接入配置 + + + +| 配置项 | 值 | 说明 | + +|--------|-----|------| + +| Base URL | `https://tokenhub.tencentmaas.com/v1` | API 端点地址 | + +| API Key | 从控制台获取 | 身份认证密钥 | + +| Model | `hy3` | 模型名称 | + +| 超时时间 | 建议 60 秒 | 避免请求被中断 | + + + +\### 速率限制 + + + +| 限制项 | 限制值 | 说明 | + +|--------|--------|------| + +| 请求频率 | 视套餐而定 | 超出会返回 429 | + +| 最大输入 Token | 视套餐而定 | 超出会截断 | + +| 最大输出 Token | 视套餐而定 | 可通过 max\_tokens 控制 | + + + + + +\## 快速开始 + + + +\### 方式一:curl(命令行) + + + +```bash + +curl https://tokenhub.tencentmaas.com/v1/chat/completions \\ + + -H "Content-Type: application/json" \\ + + -H "Authorization: Bearer 你的API密钥" \\ + + -d '{ + + "model": "hy3", + + "messages": \[ + + {"role": "user", "content": "用一句话介绍人工智能"} + + ], + + "temperature": 0.7, + + "max\_tokens": 100, + + "extra\_body": { + + "chat\_template\_kwargs": { + + "reasoning\_effort": "no\_think" + + } + + } + + }' + +``` + + + +\### 方式二:Python(OpenAI SDK) + + + +\#### 1. 安装依赖 + + + +```bash + +pip install openai python-dotenv + +``` + + + +\#### 2. 配置环境变量 + + + +创建 `.env` 文件: + + + +```env + +HY3\_BASE\_URL=https://tokenhub.tencentmaas.com/v1 + +HY3\_API\_KEY=你的API密钥 + +HY3\_MODEL=hy3 + +``` + + + +\#### 3. 最小可运行示例 + + + +```python + +import os + +from openai import OpenAI + +from dotenv import load\_dotenv + + + +load\_dotenv() + + + +client = OpenAI( + + base\_url=os.getenv("HY3\_BASE\_URL"), + + api\_key=os.getenv("HY3\_API\_KEY"), + + timeout=60.0 + +) + + + +response = client.chat.completions.create( + + model=os.getenv("HY3\_MODEL", "hy3"), + + messages=\[ + + {"role": "user", "content": "用一句话介绍人工智能"} + + ], + + temperature=0.7, + + max\_tokens=100, + + extra\_body={"chat\_template\_kwargs": {"reasoning\_effort": "no\_think"}} + +) + + + +print(response.choices\[0].message.content) + +``` + + + +\#### 4. 运行 + + + +```bash + +python your\_script.py + +``` + + + + + +\## 参数说明 + + + +\### 核心参数 + + + +| 参数 | 类型 | 必填 | 说明 | 示例 | + +|------|------|------|------|------| + +| model | string | 是 | 模型名称 | `"hy3"` | + +| messages | array | 是 | 对话消息列表 | `\[{"role":"user","content":"你好"}]` | + +| temperature | float | 否 | 随机性,0-1,越高越随机 | `0.7` | + +| top\_p | float | 否 | 核采样概率,0-1 | `0.9` | + +| max\_tokens | int | 否 | 最大输出 Token 数 | `200` | + +| stop | string/array | 否 | 停止词,遇到即停止 | `\["\\n", "END"]` | + +| stream | bool | 否 | 是否流式输出 | `True` / `False` | + +| tools | array | 否 | 工具定义列表 | 见工具调用示例 | + +| reasoning\_effort | string | 否 | 思考模式:no\_think / low / high | `"no\_think"` | + + + +\### Messages 格式 + + + +```python + +messages = \[ + + {"role": "system", "content": "你是一个编程助手"}, + + {"role": "user", "content": "你好"}, + + {"role": "assistant", "content": "你好!有什么可以帮助你的?"}, + + {"role": "user", "content": "Python 怎么读取 CSV?"} + +] + +``` + + + +| Role | 说明 | + +|------|------| + +| system | 系统提示,设定 AI 的角色和行为 | + +| user | 用户消息 | + +| assistant | 模型的回复(用于多轮对话) | + +| tool | 工具调用结果(用于工具调用场景) | + + + + + +\## 示例集合 + + + +| 示例 | 文件 | 说明 | + +|------|------|------| + +| 基础聊天 | `quickstart/01\_basic\_chat.py` | 单轮 + 多轮对话 | + +| 流式请求 | `quickstart/02\_streaming.py` | 逐字输出,提升体验 | + +| 流式 vs 非流式 | `quickstart/03\_compare.py` | 对比首 Token 时延和总耗时 | + +| 工具调用 | `quickstart/04\_tool\_calling.py` | 调用外部函数 | + +| 推理模式 | `quickstart/05\_reasoning.py` | no\_think / low / high 对比 | + +| 错误处理 | `quickstart/06\_error\_handling.py` | 重试与退避策略 | + + + +每个示例都配有详细的 `.md` 说明文档,位于 `docs/` 目录下。 + + + + + +\## 常见报错排查 + + + +\### 1. 认证错误 + + + +\*\*错误信息:\*\* + +``` + +AuthenticationError: Invalid API key + +``` + + + +\*\*原因:\*\* API Key 不正确或已过期 + + + +\*\*解决方法:\*\* + +\- 检查 `.env` 文件中的 `HY3\_API\_KEY` 是否正确 + +\- 确认 API Key 是否已激活 + +\- 尝试重新生成 API Key + + + + + +\### 2. 限流错误 + + + +\*\*错误信息:\*\* + +``` + +RateLimitError: Rate limit exceeded + +``` + + + +\*\*原因:\*\* 请求频率超过限制 + + + +\*\*解决方法:\*\* + +\- 降低请求频率 + +\- 实现指数退避重试(见示例 6) + +\- 升级套餐获取更高配额 + + + + + +\### 3. 超时错误 + + + +\*\*错误信息:\*\* + +``` + +APITimeoutError: Request timed out + +``` + + + +\*\*原因:\*\* 网络延迟或服务响应慢 + + + +\*\*解决方法:\*\* + +\- 增加 `timeout` 参数(如 60 秒) + +\- 检查网络连接 + +\- 使用流式模式获取更快反馈 + + + + + +\### 4. 模型名称错误 + + + +\*\*错误信息:\*\* + +``` + +NotFoundError: Model 'xxx' not found + +``` + + + +\*\*原因:\*\* 模型名称拼写错误 + + + +\*\*解决方法:\*\* + +\- 确认模型名称为 `hy3` + +\- 检查是否有额外空格 + + + + + +\### 5. Token 超限 + + + +\*\*错误信息:\*\* + +``` + +BadRequestError: Input tokens exceed limit + +``` + + + +\*\*原因:\*\* 输入或输出 Token 超出模型限制 + + + +\*\*解决方法:\*\* + +\- 减少输入内容长度 + +\- 减小 `max\_tokens` 值 + +\- 截断过长的对话历史 + + + + + +\### 6. 连接错误 + + + +\*\*错误信息:\*\* + +``` + +APIConnectionError: Connection refused + +``` + + + +\*\*原因:\*\* 无法连接到 API 服务 + + + +\*\*解决方法:\*\* + +\- 检查 Base URL 是否正确 + +\- 确认网络环境(是否需要代理) + +\- 检查防火墙设置 + + + + + +\### 快速排查清单 + + + +\- \[ ] Base URL 是否正确? + +\- \[ ] API Key 是否有效? + +\- \[ ] 模型名称是否为 `hy3`? + +\- \[ ] 网络是否正常? + +\- \[ ] 是否在虚拟环境中? + +\- \[ ] 依赖包是否已安装? + + + + + +\## 更多资源 + + + +\- 完整示例代码:`quickstart/` 目录 + +\- 详细文档:`docs/` 目录 + +\- GitHub 仓库:\[Tencent-Hunyuan/Hy3](https://github.com/Tencent-Hunyuan/Hy3) + diff --git a/finetune/README.md b/finetune/README.md index e132eaff..7dd2f9be 100644 --- a/finetune/README.md +++ b/finetune/README.md @@ -194,7 +194,7 @@ The key parameters in the script are as follows: - When resuming from a checkpoint, there may be minor differences in loss due to the randomness of some non-deterministic algorithms. This is normal. See: [HuggingFace Transformers Trainer Randomness](https://huggingface.co/docs/transformers/main/en/main_classes/trainer#randomness) - When `--model_name_or_path` is specified, all model-related parameters will be ignored. - Samples within a batch are padded to the length of the longest sample in the batch, but the maximum length of each sample is `max_seq_length`. Any excess will be truncated. -- If you see a warning about bias weights not being loaded, you can ignore it. Hunyuan-Large does not use bias. +- If you see a warning about **linear layer** bias weights not being loaded, you can ignore it; Hy3's linear layers (q_proj / k_proj / v_proj / o_proj, etc.) do not use bias. Note: the MoE router's `e_score_correction_bias` is a buffer and is auto-loaded by the training script, so please do not ignore its loading failure. ##### What if GPU Memory is Insufficient? diff --git a/finetune/README_CN.md b/finetune/README_CN.md index bdcb1384..768c2687 100644 --- a/finetune/README_CN.md +++ b/finetune/README_CN.md @@ -194,7 +194,7 @@ Are you sure you want to continue connecting (yes/no)? - 从 ckpt 继续训练时,loss 可能会有微小的偏差,这是由一些非确定性算法带来的随机性,是正常现象。参考:[HuggingFace Transformers Trainer Randomness](https://huggingface.co/docs/transformers/main/en/main_classes/trainer#randomness) - 当 `--model_name_or_path` 有效时,所有模型相关的参数都会被忽略 - 一个 batch 内的样本会通过 padding 对齐 batch 内最长的样本,而每条样本的长度最长为 max_seq_length,超出的部分会被裁剪 -- 如果报出 bias 权重没有 load 的 warning,忽略即可,Hunyuan-Large 中不会用到 bias +- 如果报出**线性层** bias 权重没有 load 的 warning,忽略即可,Hy3 的线性层(q_proj / k_proj / v_proj / o_proj 等)不使用 bias。注意:MoE 路由的 `e_score_correction_bias` 属于 buffer,已由训练脚本自动加载,如果加载失败请不要忽略。 ##### 显存不足怎么办? diff --git a/quickstart/01_basic_chat.py b/quickstart/01_basic_chat.py new file mode 100644 index 00000000..6af32c0e --- /dev/null +++ b/quickstart/01_basic_chat.py @@ -0,0 +1,63 @@ +"""示例 1: 基础聊天(单轮 + 多轮)""" + +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from utils import get_client, print_response, MODEL + +def main(): + print("\n🚀 Hy3 基础聊天示例") + print("="*50) + + client = get_client() + + # ============ 单轮对话 ============ + print("\n📌 1. 单轮对话") + + response = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "user", "content": "用一句话总结什么是 RESTful API。"} + ], + temperature=0.7, + top_p=1.0, + max_tokens=200, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + + print_response("单轮对话", response) + + # ============ 多轮对话 ============ + print("\n📌 2. 多轮对话") + + messages = [ + {"role": "system", "content": "你是一个 Python 编程助手。"}, + {"role": "user", "content": "Python 中如何读取 CSV 文件?"} + ] + + print("\n🔄 第 1 轮:") + response1 = client.chat.completions.create( + model=MODEL, + messages=messages, + temperature=0.3, + max_tokens=300, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + print_response("多轮 - 第1轮", response1) + + messages.append({"role": "assistant", "content": response1.choices[0].message.content}) + messages.append({"role": "user", "content": "那如何把 CSV 数据存入 MySQL 数据库?"}) + + print("\n🔄 第 2 轮:") + response2 = client.chat.completions.create( + model=MODEL, + messages=messages, + temperature=0.3, + max_tokens=500, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + print_response("多轮 - 第2轮", response2) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/quickstart/02_streaming.py b/quickstart/02_streaming.py new file mode 100644 index 00000000..347549aa --- /dev/null +++ b/quickstart/02_streaming.py @@ -0,0 +1,54 @@ +"""示例 2: 流式请求(逐字输出)""" + +import sys +import os +import time +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from utils import get_client, MODEL + +def main(): + print("\n🌊 Hy3 流式请求示例") + print("="*50) + + client = get_client() + + print("\n📌 流式输出(逐字显示):") + print("-" * 30) + + start_time = time.time() + + stream = client.chat.completions.create( + model=MODEL, + messages=[ + {"role": "user", "content": "用一句话解释什么是 Docker,然后列举 3 个常用命令。"} + ], + temperature=0.7, + max_tokens=200, + stream=True, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + + full_response = "" + chunk_count = 0 + first_chunk_time = None + + for chunk in stream: + chunk_count += 1 + if first_chunk_time is None and chunk.choices[0].delta.content: + first_chunk_time = time.time() + if chunk.choices[0].delta.content: + content = chunk.choices[0].delta.content + print(content, end="", flush=True) + full_response += content + + end_time = time.time() + + print(f"\n\n📊 统计信息:") + print(f" - 总块数: {chunk_count}") + print(f" - 首 Token 时延: {first_chunk_time - start_time:.2f} 秒") + print(f" - 总耗时: {end_time - start_time:.2f} 秒") + print(f" - 输出长度: {len(full_response)} 字符") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/quickstart/03_compare.py b/quickstart/03_compare.py new file mode 100644 index 00000000..4fe68703 --- /dev/null +++ b/quickstart/03_compare.py @@ -0,0 +1,93 @@ +"""示例 3: 流式 vs 非流式对比""" + +import sys +import os +import time +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from utils import get_client, MODEL + +def test_non_streaming(client, prompt): + """测试非流式请求""" + print(" ⏳ 等待完整响应...") + start = time.time() + + response = client.chat.completions.create( + model=MODEL, + messages=[{"role": "user", "content": prompt}], + temperature=0.7, + max_tokens=150, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + + elapsed = time.time() - start + content = response.choices[0].message.content + + print(f" ✅ 完成 ({elapsed:.2f}秒)") + print(f" 📝 内容: {content[:60]}...") + print(f" 📊 Token: {response.usage.total_tokens}") + + return elapsed, len(content) + +def test_streaming(client, prompt): + """测试流式请求""" + print(" 🌊 开始流式接收...") + start = time.time() + + stream = client.chat.completions.create( + model=MODEL, + messages=[{"role": "user", "content": prompt}], + temperature=0.7, + max_tokens=150, + stream=True, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + + first_token_time = None + content = "" + + for chunk in stream: + if first_token_time is None and chunk.choices[0].delta.content: + first_token_time = time.time() + if chunk.choices[0].delta.content: + content += chunk.choices[0].delta.content + + elapsed = time.time() - start + first_token_latency = first_token_time - start if first_token_time else 0 + + print(f" ✅ 完成 ({elapsed:.2f}秒)") + print(f" ⚡ 首 Token 时延: {first_token_latency:.2f}秒") + print(f" 📝 内容: {content[:60]}...") + print(f" 📊 长度: {len(content)} 字符") + + return elapsed, first_token_latency, len(content) + +def main(): + print("\n⚖️ Hy3 流式 vs 非流式对比") + print("="*50) + + client = get_client() + prompt = "什么是微服务架构?请用 100 字左右概括其主要特点。" + + print(f"\n📌 测试 Prompt: {prompt}\n") + + print("🔹 非流式模式:") + non_stream_time, non_stream_len = test_non_streaming(client, prompt) + + print("\n🔸 流式模式:") + stream_time, first_token, stream_len = test_streaming(client, prompt) + + print("\n" + "="*50) + print("📊 对比结果:") + print("="*50) + print(f" 非流式总耗时: {non_stream_time:.2f}秒") + print(f" 流式总耗时: {stream_time:.2f}秒") + print(f" 流式首 Token 时延: {first_token:.2f}秒") + print(f" 内容长度: 非流式 {non_stream_len} 字符 | 流式 {stream_len} 字符") + print("\n💡 结论:") + print(" - 流式可以更快看到首 Token,提升用户体验") + print(" - 非流式总耗时可能略短,但用户需等待完整响应") + print(" - 实时交互场景推荐使用流式") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/quickstart/04_tool_calling.py b/quickstart/04_tool_calling.py new file mode 100644 index 00000000..c0d0594d --- /dev/null +++ b/quickstart/04_tool_calling.py @@ -0,0 +1,156 @@ +"""示例 4: 工具调用""" + +import sys +import os +import json +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from utils import get_client, MODEL + +tools = [ + { + "type": "function", + "function": { + "name": "get_current_time", + "description": "获取当前日期和时间", + "parameters": { + "type": "object", + "properties": { + "format": { + "type": "string", + "enum": ["full", "date", "time"], + "description": "时间格式" + } + } + } + } + }, + { + "type": "function", + "function": { + "name": "calculate", + "description": "执行数学运算", + "parameters": { + "type": "object", + "properties": { + "operation": { + "type": "string", + "enum": ["add", "subtract", "multiply", "divide"], + "description": "运算类型" + }, + "a": {"type": "number"}, + "b": {"type": "number"} + }, + "required": ["operation", "a", "b"] + } + } + } +] + +def get_current_time(format_type="full"): + """模拟获取时间""" + from datetime import datetime + now = datetime.now() + if format_type == "date": + return now.strftime("%Y-%m-%d") + elif format_type == "time": + return now.strftime("%H:%M:%S") + return now.strftime("%Y-%m-%d %H:%M:%S") + +def calculate(operation, a, b): + if operation == "add": return str(a + b) + elif operation == "subtract": return str(a - b) + elif operation == "multiply": return str(a * b) + elif operation == "divide": return str(a / b) if b != 0 else "错误:除数不能为0" + return "未知操作" + +def main(): + print("\n🔧 Hy3 工具调用示例") + print("="*50) + + client = get_client() + + # 测试 1: 查询时间 + print("\n📌 示例 1: 查询当前时间") + print("-" * 30) + + messages = [{"role": "user", "content": "现在几点了?"}] + + response = client.chat.completions.create( + model=MODEL, + messages=messages, + tools=tools, + tool_choice="auto", + temperature=0.7, + max_tokens=200, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + + print(f"💬 用户: {messages[0]['content']}") + + if response.choices[0].message.tool_calls: + tool_call = response.choices[0].message.tool_calls[0] + func_name = tool_call.function.name + args = json.loads(tool_call.function.arguments) + + print(f"🔧 调用工具: {func_name}({args})") + + if func_name == "get_current_time": + result = get_current_time(args.get("format", "full")) + else: + result = "未知工具" + + print(f"📊 工具返回: {result}") + + messages.append(response.choices[0].message) + messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result}) + + final = client.chat.completions.create( + model=MODEL, + messages=messages, + temperature=0.7, + max_tokens=200, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + print(f"🤖 AI 回复: {final.choices[0].message.content}") + + # 测试 2: 计算 + print("\n📌 示例 2: 数学计算") + print("-" * 30) + + messages = [{"role": "user", "content": "25 乘以 37 等于多少?"}] + + response = client.chat.completions.create( + model=MODEL, + messages=messages, + tools=tools, + tool_choice="auto", + temperature=0.7, + max_tokens=200, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + + print(f"💬 用户: {messages[0]['content']}") + + if response.choices[0].message.tool_calls: + tool_call = response.choices[0].message.tool_calls[0] + args = json.loads(tool_call.function.arguments) + + print(f"🔧 调用工具: calculate({args})") + result = calculate(args["operation"], args["a"], args["b"]) + print(f"📊 计算结果: {result}") + + messages.append(response.choices[0].message) + messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result}) + + final = client.chat.completions.create( + model=MODEL, + messages=messages, + temperature=0.7, + max_tokens=200, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + print(f"🤖 AI 回复: {final.choices[0].message.content}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/quickstart/05_reasoning.py b/quickstart/05_reasoning.py new file mode 100644 index 00000000..e0226ec8 --- /dev/null +++ b/quickstart/05_reasoning.py @@ -0,0 +1,42 @@ +"""示例 5: 推理模式对比""" + +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from utils import get_client, MODEL + +def test_mode(client, prompt, mode): + print(f"\n🔍 推理模式: {mode}") + print("-" * 30) + + response = client.chat.completions.create( + model=MODEL, + messages=[{"role": "user", "content": prompt}], + temperature=0.7, + max_tokens=400, + extra_body={"chat_template_kwargs": {"reasoning_effort": mode}} + ) + + msg = response.choices[0].message + reasoning = getattr(msg, "reasoning_content", None) or getattr(msg, "reasoning", None) + + print(f"📊 Token 用量: {response.usage.total_tokens}") + print(f"🧠 思考过程: {'有' if reasoning else '无(直接输出)'}") + print(f"💬 回答预览: {msg.content[:80]}...") + return response + +def main(): + print("\n🧠 Hy3 推理模式对比") + print("="*50) + + client = get_client() + prompt = "一个班级有 40 个学生,男生比女生多 6 人,问男生和女生各有多少人?请一步步解释。" + + print(f"\n📌 测试问题: {prompt}\n") + + for mode in ["no_think", "low", "high"]: + test_mode(client, prompt, mode) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/quickstart/06_error_handling.py b/quickstart/06_error_handling.py new file mode 100644 index 00000000..0159107d --- /dev/null +++ b/quickstart/06_error_handling.py @@ -0,0 +1,60 @@ +"""示例 6: 错误处理与重试""" + +import sys +import os +import time +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from utils import get_client, MODEL + +def call_with_retry(max_retries=3): + def decorator(func): + def wrapper(*args, **kwargs): + for i in range(max_retries): + try: + return func(*args, **kwargs) + except Exception as e: + print(f"⚠️ 第 {i+1} 次尝试失败: {type(e).__name__}") + if i == max_retries - 1: + raise + wait = 2 ** i + print(f"⏳ 等待 {wait} 秒后重试...") + time.sleep(wait) + return None + return wrapper + return decorator + +def main(): + print("\n🛡️ Hy3 错误处理示例") + print("="*50) + + client = get_client() + + @call_with_retry(max_retries=3) + def safe_call(): + return client.chat.completions.create( + model=MODEL, + messages=[{"role": "user", "content": "你好,请简单介绍一下自己。"}], + max_tokens=80, + extra_body={"chat_template_kwargs": {"reasoning_effort": "no_think"}} + ) + + try: + print("\n📌 正常调用(带重试保护):") + response = safe_call() + print(f"✅ 成功! 回复: {response.choices[0].message.content[:60]}...") + except Exception as e: + print(f"❌ 最终失败: {e}") + + print("\n" + "="*50) + print("💡 错误处理最佳实践:") + print(""" + 1. 使用 try-except 捕获异常 + 2. 对限流/超时使用指数退避重试 + 3. 设置合理的超时时间 + 4. 记录错误日志便于调试 + 5. 设置最大重试次数避免无限循环 + """) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/rl/README.md b/rl/README.md new file mode 100644 index 00000000..b07fe3da --- /dev/null +++ b/rl/README.md @@ -0,0 +1,216 @@ +# Hy3 Reinforcement Learning Training + +English | [简体中文](./README_CN.md) + +This document describes how to run reinforcement learning training for Hy3 with [verl](https://github.com/volcengine/verl). Training runs on [Megatron-LM](https://github.com/NVIDIA/Megatron-LM); NVIDIA [Megatron-Bridge](https://github.com/NVIDIA-NeMo/Megatron-Bridge) (`HYV3Bridge`) converts the HF checkpoint into a Megatron model on the fly at training startup — no offline conversion needed. Rollout runs on [vLLM](https://github.com/vllm-project/vllm). + +Training script: [`examples/grpo_trainer/run_hy_v3_megatron.sh`](https://github.com/verl-project/verl/blob/main/examples/grpo_trainer/run_hy_v3_megatron.sh) in the verl repository. + +## Quick Start + +### Environment + +Recommended image: `verlai/verl:vllm023.dev1` + +Verified version combination: + +| Component | Version | +| --- | --- | +| verl | [220e903](https://github.com/verl-project/verl/commit/220e9039902c6db56860e2afd659803dc34ec005) | +| Megatron-Bridge | [df0852c](https://github.com/NVIDIA-NeMo/Megatron-Bridge/commit/df0852cf94c674de07f9f7ef933c484de8aca505) | +| transformers | 5.6.0 | +| nvidia-modelopt | 0.44.0rc5 | + +### Prepare dependencies + +Clone verl at the verified version, then clone the dependencies into `third_party/` under the repository root: + +```bash +git clone https://github.com/verl-project/verl.git +cd verl +git checkout 220e903 + +mkdir third_party +git clone https://github.com/NVIDIA-NeMo/Megatron-Bridge.git third_party/Megatron-Bridge +git -C third_party/Megatron-Bridge checkout df0852c + +git clone https://github.com/Ascend/TransferQueue.git third_party/TransferQueue +``` + +Create a `runtime_env.yaml` so the Ray runtime env ships the working directory (including `third_party/`) to every worker node and adds the dependencies to `PYTHONPATH`: + +```yaml +# runtime_env.yaml +working_dir: ./ +excludes: [ + "/.git/", + "/third_party/Megatron-Bridge/.git/", + "/third_party/TransferQueue/.git/", + "**/__pycache__/", +] +env_vars: + PYTHONPATH: third_party/Megatron-Bridge/src:third_party/TransferQueue +``` + +### Prepare the dataset + +The script defaults to [DAPO-Math-17k](https://huggingface.co/datasets/BytedTsinghua-SIA/DAPO-Math-17k) (train) and [AIME-2024](https://huggingface.co/datasets/BytedTsinghua-SIA/AIME-2024) (validation). Both are already in the format verl expects on HuggingFace — just download and dump to parquet: + +```python +# prepare_data.py +import datasets + +dapo = datasets.load_dataset("BytedTsinghua-SIA/DAPO-Math-17k", "default")["train"] +dapo.to_parquet("DAPO-Math-17k/dapo-math-17k.parquet") + +aime = datasets.load_dataset("BytedTsinghua-SIA/AIME-2024", "default")["train"] +aime.to_parquet("AIME-2024/aime-2024.parquet") +``` + +```bash +python prepare_data.py # output layout matches the script's default DATA_DIR +``` + +Place the data under the verl repository root (`DATA_DIR` defaults to `$PWD`), or point elsewhere with `DATA_DIR=/path/to/data`. + +### Submit the job + +Use the HuggingFace checkpoint directly; training data is in parquet format. Point `RAY_ADDRESS` at the cluster head node, then submit with `ray job submit`: + +```bash +export RAY_ADDRESS=http://: + +ray job submit --no-wait --runtime-env=runtime_env.yaml -- \ + bash examples/grpo_trainer/run_hy_v3_megatron.sh +``` + +> **Note**: on H20, rollout (vLLM inference) requires at least TP=16 (the script defaults to `ROLLOUT_TP=16`) — the weights of a single instance only fit when sharded across 16 GPUs. + +### Key parameters + +**Hy3 settings** + +| Parameter | Value | Reason | +| --- | --- | --- | +| `moe_router_enable_expert_bias` | True | Hy3 routes with a per-expert bias (aux-loss-free) | +| `moe_router_bias_update_rate` | 0 | 0 freezes the bias (it still participates in scoring but is never updated) | +| `moe_router_load_balancing_type` | none | no auxiliary load-balancing loss | + +**Training setup** + +| Parameter | What it does | +| --- | --- | +| `data.train_batch_size` | prompts sampled per step | +| `rollout.n` | responses generated per prompt, i.e. the GRPO group size (the in-group advantage baseline is computed over them), typically 8–16 | +| `actor.ppo_mini_batch_size` | samples per actor parameter update | +| `actor.optim.lr` | actor learning rate, typically the 1e-6 scale | +| `data.max_response_length` | maximum generation length, task-dependent | + +**Algorithm behavior** + +| Parameter | What it does | How to set it | +| --- | --- | --- | +| `algorithm.norm_adv_by_std_in_grpo` | whether advantages are divided by the in-group std | True is original GRPO; False is the [Dr.GRPO](https://arxiv.org/abs/2503.20783) fix, preventing too-easy/too-hard prompts with tiny variance from being amplified | +| `actor.clip_ratio_low` / `clip_ratio_high` | PPO trust-region bounds | a slightly relaxed upper bound (clip-higher, e.g. 0.2/0.28) gives low-probability tokens more room to rise, mitigating entropy collapse | +| `actor.clip_ratio_c` | [dual-clip](https://arxiv.org/abs/1912.09729) lower-bound constant | caps the penalty on negative-advantage tokens to keep a single step from blowing up the policy | +| `actor.kl_loss_coef` | KL-regularization strength toward the reference policy | 0 means KL-free (rely on clipping); add a small value (e.g. 1e-3) if training is unstable | +| `algorithm.rollout_correction.rollout_is` / `rollout_is_threshold` | correction for the rollout/training log-prob mismatch (IcePop) | `token` plus lower/upper bounds (e.g. `0.5_4.0`; token weights outside the range are zeroed); recommended whenever the numeric gap between the two engines is non-negligible | +| `rollout.temperature` / `top_p` | rollout sampling exploration strength | typically 0.9–1.0; too low a temperature reduces in-group diversity and degrades the GRPO baseline | + +**Memory and sequence length** + +Parameters that must move together when extending the sequence length (changing `data.max_response_length` alone is not enough): + +| Parameter | Coupling | +| --- | --- | +| `data.max_response_length` | target response length | +| `rollout.max_model_len` | vLLM context length, must be ≥ prompt + response | +| `actor.ppo_max_token_len_per_gpu` | training-side per-GPU token budget; sequences are split across CP ranks, so each GPU actually holds `(prompt+response)/CP` per sequence — the requirement is budget × CP ≥ prompt + response | +| `actor.megatron.context_parallel_size` | scale up proportionally for much longer sequences (activation memory grows linearly with sequence length and is sharded by CP) | + +For training-side OOM (the error occurs during actor update / log_prob), you can try: + +1. Lower `actor.ppo_max_token_len_per_gpu` — dynamic batching packs micro batches against it, so it directly bounds the activation peak; +2. Set `actor.ppo_micro_batch_size_per_gpu` to 1; +3. Lower `ref.log_prob_max_token_len_per_gpu` / `rollout.log_prob_max_token_len_per_gpu`; +4. Raise `actor.megatron.context_parallel_size` (activations are sharded by CP) or `pipeline_model_parallel_size` (fewer layers per stage). + +Offload and recompute: + +| Config | Script default | What it does | +| --- | --- | --- | +| `actor.megatron.param_offload` | True | offloads parameters to CPU while training is idle, freeing GPU memory for rollout | +| `actor.megatron.optimizer_offload` | True | offloads optimizer state (fp32 master weights + momenta — the biggest memory consumer) to CPU | +| `actor.megatron.grad_offload` | True | offloads gradient buffers to CPU | +| `override_transformer_config.recompute_granularity` | full | full activation recomputation: forward stores no activations, backward recomputes them — trades ~30% extra compute for most of the activation memory | +| `override_transformer_config.recompute_method` / `recompute_num_layers` | uniform / 1 | recompute uniformly at 1-layer granularity — the finest granularity, lowest peak | + +Both substantially relieve OOM: offload keeps parameters/optimizer state/gradients out of GPU memory at the cost of per-step CPU↔GPU transfers, and recompute keeps activations out of GPU memory at the cost of one extra forward pass during backward. + +## Customizing Your Training + +### Using your own dataset + +verl reads parquet data where each row contains 5 fields (see the verl docs: [Prepare Data](https://verl.readthedocs.io/en/latest/preparation/prepare_data.html)): + +```python +{ + "data_source": "my_dataset", # dataset name; the RewardManager uses it to index the scoring function + "prompt": [ # HuggingFace chat template format; the tokenizer renders and tokenizes it + {"role": "user", "content": "1+1=?"} + ], + "ability": "math", # task category + "reward_model": { + "style": "rule", + "ground_truth": "2" # reference answer; the reward function's scoring logic must align with its format + }, + "extra_info": {"split": "train", "index": 0}, # metadata +} +``` + +Write a preprocessing script that converts your data into this format (verl's [`examples/data_preprocess/`](https://github.com/verl-project/verl/tree/main/examples/data_preprocess) ships a dozen ready-to-adapt templates — GSM8K, MATH, etc.), save as parquet, then point the script at your files via environment variables: + +```bash +TRAIN_FILES=/path/to/my_train.parquet \ +VAL_FILES=/path/to/my_val.parquet \ +bash examples/grpo_trainer/run_hy_v3_megatron.sh +``` + +**Reward function**: math-style tasks with rule-verifiable answers can reuse the default DAPO reward as-is (it routes to a built-in scoring function by `data_source`); other tasks need a custom reward function, specified via `custom_reward_function.path` (see the verl docs: [Implement Reward Function](https://verl.readthedocs.io/en/latest/preparation/reward_function.html)). + +### Switching the RL algorithm + +The script defaults to GRPO, but the algorithm layer is decoupled from the model layer, so switching algorithms requires no changes to the Hy3-specific configuration. + +**Switch between built-in algorithms (one config key)**: verl ships a dozen advantage estimators, selected via `algorithm.adv_estimator` — options include `gae` (PPO), `grpo`, `rloo`, `remax`, `reinforce_plus_plus`, `opo`, `gpg`, etc. (full list in `AdvantageEstimator` in [`core_algos.py`](https://github.com/verl-project/verl/blob/main/verl/trainer/ppo/core_algos.py)); the policy loss is selected via `actor_rollout_ref.actor.policy_loss.loss_mode` (`vanilla`, `gspo`, `cispo`, `clip_cov`, etc.). See the verl docs for each algorithm's theory and configuration: [PPO](https://verl.readthedocs.io/en/latest/algo/ppo.html) / [GRPO](https://verl.readthedocs.io/en/latest/algo/grpo.html) / [DAPO](https://verl.readthedocs.io/en/latest/algo/dapo.html). + +**Training Hy3 with a recipe**: full-pipeline algorithms (e.g. DAPO with dynamic sampling) live as standalone implementations under verl's [`recipe/`](https://github.com/verl-project/verl/tree/main/recipe) directory, each with its own launch script. When pointing a recipe at Hy3, carry over the following required Hy3 settings into the recipe's launch script: + +```bash +# Required Hy3 settings (apply to any recipe) +actor_rollout_ref.model.path=/path/to/Hy3 +actor_rollout_ref.model.trust_remote_code=True +data.trust_remote_code=True +actor_rollout_ref.actor.megatron.use_mbridge=True +actor_rollout_ref.actor.megatron.vanilla_mbridge=False ++actor_rollout_ref.actor.megatron.override_transformer_config.moe_router_enable_expert_bias=True ++actor_rollout_ref.actor.megatron.override_transformer_config.moe_router_bias_update_rate=0 ++actor_rollout_ref.actor.megatron.override_transformer_config.moe_router_load_balancing_type=none ++actor_rollout_ref.actor.megatron.override_transformer_config.moe_grouped_gemm=True +``` + +For implementing entirely new algorithms, see the verl docs: [Extend to other RL algorithms](https://verl.readthedocs.io/en/latest/advance/dpo_extension.html). + +## Results + +We launched Hy3 GRPO training on 128 H20 GPUs (16 nodes × 8) with [`run_hy_v3_megatron.sh`](https://github.com/verl-project/verl/blob/main/examples/grpo_trainer/run_hy_v3_megatron.sh): math reasoning on the DAPO dataset at 8192 max response length, with PP/CP/EP parallelism plus full offload, and BF16 rollout + BF16 training. + +Exact values used in this run: batch 128 prompts × 16 samples (2048 trajectories/step), `ppo_mini_batch_size=128` (one update per step), lr 1e-6, clip 0.2/0.28 (dual-clip c=10.0), KL-free, DAPO overlong buffer (len 4096 / penalty 1.0), IcePop threshold `0.5_4.0`, sampling temperature 0.9 / top_p 1.0. + +The training dynamics are stable: the rollout/training log-prob drift (`rollout_probs_diff`) stays below 0.015 throughout, and both reward and the AIME validation score grow steadily over the run. + +![Hy3 RL training curves](../assets/rl-training.png) + +## Acknowledgements + +We thank the Tencent Hunyuan team for their support on model training and engineering infrastructure, as well as the [verl](https://github.com/volcengine/verl), [Megatron-Bridge](https://github.com/NVIDIA-NeMo/Megatron-Bridge), [Megatron-LM](https://github.com/NVIDIA/Megatron-LM), and [vLLM](https://github.com/vllm-project/vllm) communities for their help. diff --git a/rl/README_CN.md b/rl/README_CN.md new file mode 100644 index 00000000..45e104e5 --- /dev/null +++ b/rl/README_CN.md @@ -0,0 +1,216 @@ +# Hy3 强化学习训练 + +[English](./README.md) | 简体中文 + +本文档介绍如何使用 [verl](https://github.com/volcengine/verl) 对 Hy3 进行强化学习训练。训练侧使用 [Megatron-LM](https://github.com/NVIDIA/Megatron-LM),并通过 NVIDIA [Megatron-Bridge](https://github.com/NVIDIA-NeMo/Megatron-Bridge)(`HYV3Bridge`)在训练启动时将 HF checkpoint 在线转换为 Megatron 模型,无需离线转换;rollout 侧使用 [vLLM](https://github.com/vllm-project/vllm)。 + +训练脚本:verl 仓库 [`examples/grpo_trainer/run_hy_v3_megatron.sh`](https://github.com/verl-project/verl/blob/main/examples/grpo_trainer/run_hy_v3_megatron.sh) + +## 快速开始 + +### 环境 + +推荐镜像:`verlai/verl:vllm023.dev1` + +已验证的版本组合: + +| 组件 | 版本 | +| --- | --- | +| verl | [220e903](https://github.com/verl-project/verl/commit/220e9039902c6db56860e2afd659803dc34ec005) | +| Megatron-Bridge | [df0852c](https://github.com/NVIDIA-NeMo/Megatron-Bridge/commit/df0852cf94c674de07f9f7ef933c484de8aca505) | +| transformers | 5.6.0 | +| nvidia-modelopt | 0.44.0rc5 | + +### 准备依赖 + +clone verl 并切换到已验证版本,然后在仓库根目录下将依赖 clone 到 `third_party/`: + +```bash +git clone https://github.com/verl-project/verl.git +cd verl +git checkout 220e903 + +mkdir third_party +git clone https://github.com/NVIDIA-NeMo/Megatron-Bridge.git third_party/Megatron-Bridge +git -C third_party/Megatron-Bridge checkout df0852c + +git clone https://github.com/Ascend/TransferQueue.git third_party/TransferQueue +``` + +编写 `runtime_env.yaml`,通过 Ray runtime env 将工作目录(含 `third_party/`)分发到各 worker 节点,并加入 `PYTHONPATH`: + +```yaml +# runtime_env.yaml +working_dir: ./ +excludes: [ + "/.git/", + "/third_party/Megatron-Bridge/.git/", + "/third_party/TransferQueue/.git/", + "**/__pycache__/", +] +env_vars: + PYTHONPATH: third_party/Megatron-Bridge/src:third_party/TransferQueue +``` + +### 准备数据集 + +脚本默认使用 [DAPO-Math-17k](https://huggingface.co/datasets/BytedTsinghua-SIA/DAPO-Math-17k)(训练)和 [AIME-2024](https://huggingface.co/datasets/BytedTsinghua-SIA/AIME-2024)(验证),两者在 HuggingFace 上已是 verl 所需格式,下载后转存 parquet 即可: + +```python +# prepare_data.py +import datasets + +dapo = datasets.load_dataset("BytedTsinghua-SIA/DAPO-Math-17k", "default")["train"] +dapo.to_parquet("DAPO-Math-17k/dapo-math-17k.parquet") + +aime = datasets.load_dataset("BytedTsinghua-SIA/AIME-2024", "default")["train"] +aime.to_parquet("AIME-2024/aime-2024.parquet") +``` + +```bash +python prepare_data.py # 输出路径与脚本默认的 DATA_DIR 布局一致 +``` + +数据放在 verl 仓库根目录(`DATA_DIR` 默认为 `$PWD`)下,或通过 `DATA_DIR=/path/to/data` 指定。 + +### 提交任务 + +模型直接使用 HuggingFace 格式 checkpoint,训练数据为 parquet 格式。设置 `RAY_ADDRESS` 指向集群 head 节点后,通过 `ray job submit` 提交: + +```bash +export RAY_ADDRESS=http://: + +ray job submit --no-wait --runtime-env=runtime_env.yaml -- \ + bash examples/grpo_trainer/run_hy_v3_megatron.sh +``` + +> **注**:H20 上 rollout(vLLM 推理)最小需要 TP=16(脚本默认 `ROLLOUT_TP=16`),单实例权重需跨 16 卡切分才放得下。 + +### 关键参数 + +**Hy3配置** + +| 参数 | 值 | 原因 | +| --- | --- | --- | +| `moe_router_enable_expert_bias` | True | Hy3 使用 per-expert bias 路由(aux-loss-free) | +| `moe_router_bias_update_rate` | 0 | 设 0 冻结 bias(只参与打分、不再更新)。 | +| `moe_router_load_balancing_type` | none | 不使用辅助负载均衡 loss | + +**训练设置** + +| 参数 | 作用 | +| --- | --- | +| `data.train_batch_size` | 每步采样的 prompt 数 | +| `rollout.n` | 每个 prompt 生成的 response 数,即 GRPO 组大小(组内计算 advantage 基线),常用 8–16; +| `actor.ppo_mini_batch_size` | actor 每次参数更新的样本量 | +| `actor.optim.lr` | actor 学习率,常用 1e-6 量级 | +| `data.max_response_length` | 最长生成长度,依任务而定 | + +**算法行为** + +| 参数 | 作用 | 怎么设 | +| --- | --- | --- | +| `algorithm.norm_adv_by_std_in_grpo` | advantage 是否除以组内 std | True 为原版 GRPO;False 为 [Dr.GRPO](https://arxiv.org/abs/2503.20783) 修正,避免过易/过难样本因方差小被放大 | +| `actor.clip_ratio_low` / `clip_ratio_high` | PPO 信任域上下界 | 上界略放宽(clip-higher,如 0.2/0.28)给低概率 token 更多上升空间,缓解熵坍缩 | +| `actor.clip_ratio_c` | [dual-clip](https://arxiv.org/abs/1912.09729) 下界常数 | 封顶负 advantage token 的惩罚,防止策略被单步拉爆 | +| `actor.kl_loss_coef` | 向参考策略的 KL 正则强度 | 0 为 KL-free(靠 clip 约束);训练不稳时可加小值(如 1e-3) | +| `algorithm.rollout_correction.rollout_is` / `rollout_is_threshold` | rollout 与训练引擎的 log-prob 偏差校正(IcePop) | `token` + 上下阈值(如 `0.5_4.0`,权重超界的 token 置零);两侧引擎精度偏差不可忽略时建议开启 | +| `rollout.temperature` / `top_p` | rollout 采样探索强度 | 常用 0.9–1.0;温度过低组内多样性不足,GRPO 组内基线退化 | + +**显存与序列长度** + +| 参数 | 联动关系 | +| --- | --- | +| `data.max_response_length` | 目标响应长度 | +| `rollout.max_model_len` | vLLM 上下文长度,需 ≥ prompt + response | +| `actor.ppo_max_token_len_per_gpu` | 训练侧每卡 token 预算;序列被 CP 切分到多卡,单条序列每卡实际占 `(prompt+response)/CP`,故需满足 预算 × CP ≥ prompt + response | +| `actor.megatron.context_parallel_size` | 序列显著变长时按比例增大(激活显存随序列长度线性增长,由 CP 分摊) | + +训练侧 OOM(报错发生在 actor update / log_prob 阶段),可尝试: + +1. 调小 `actor.ppo_max_token_len_per_gpu`——动态 batch 按它打包 micro batch,直接决定激活峰值; +2. `actor.ppo_micro_batch_size_per_gpu` 调为 1; +3. 调小 `ref.log_prob_max_token_len_per_gpu` / `rollout.log_prob_max_token_len_per_gpu`; +4. 增大 `actor.megatron.context_parallel_size`(激活按 CP 分摊)或 `pipeline_model_parallel_size`(每 stage 层数更少)。 + +offload 与 recompute: + +| 配置 | 脚本默认 | 作用 | +| --- | --- | --- | +| `actor.megatron.param_offload` | True | 训练空闲期把参数下放 CPU,给 rollout 腾显存 | +| `actor.megatron.optimizer_offload` | True | 优化器状态(fp32 master 权重 + 动量,显存大头)放 CPU | +| `actor.megatron.grad_offload` | True | 梯度缓冲放 CPU | +| `override_transformer_config.recompute_granularity` | full | 全量激活重算:前向不存激活、反向重算,用约 30% 额外计算换掉大部分激活显存 | +| `override_transformer_config.recompute_method` / `recompute_num_layers` | uniform / 1 | 按每 1 层为单位均匀重算,粒度最细、峰值最低 | + +两者都能显著缓解 OOM,offload 用每步 CPU↔GPU 搬运耗时换取参数/优化器/梯度不占显存,recompute 用反向多一次前向计算换取激活不占显存。 + +## 自定义训练 + +### 使用自己的数据集 + +verl 读取 parquet 格式数据,每行需包含 5 个字段(详见 verl 官方文档 [Prepare Data](https://verl.readthedocs.io/en/latest/preparation/prepare_data.html)): + +```python +{ + "data_source": "my_dataset", # 数据集名称,RewardManager 据此索引对应的打分函数 + "prompt": [ # HuggingFace chat template 格式,tokenizer 会渲染并分词 + {"role": "user", "content": "1+1=?"} + ], + "ability": "math", # 任务类别 + "reward_model": { + "style": "rule", + "ground_truth": "2" # 标准答案;reward 函数的判分逻辑须与其格式对齐 + }, + "extra_info": {"split": "train", "index": 0}, # 元信息 +} +``` + +编写预处理脚本转换为上述格式(verl 的 [`examples/data_preprocess/`](https://github.com/verl-project/verl/tree/main/examples/data_preprocess) 提供了 GSM8K、MATH 等十余个可直接套用的模板),保存为 parquet 后通过环境变量指定: + +```bash +TRAIN_FILES=/path/to/my_train.parquet \ +VAL_FILES=/path/to/my_val.parquet \ +bash examples/grpo_trainer/run_hy_v3_megatron.sh +``` + +**reward 函数**:答案可规则判分的数学类任务可直接沿用默认的 DAPO reward(按 `data_source` 自动路由到内置打分函数);其他任务需自定义 reward 函数,通过 `custom_reward_function.path` 指定(见 verl 文档 [Implement Reward Function](https://verl.readthedocs.io/en/latest/preparation/reward_function.html))。 + + +### 更换 RL 算法 + +训练脚本默认 GRPO,但算法层与模型层解耦,换算法时 Hy3 相关配置无需改动。 + +**切换内置算法(改一个配置项)**:verl 内置了十余种 advantage 估计器,通过 `algorithm.adv_estimator` 直接切换,可选值包括 `gae`(PPO)、`grpo`、`rloo`、`remax`、`reinforce_plus_plus`、`opo`、`gpg` 等(完整列表见 [`core_algos.py`](https://github.com/verl-project/verl/blob/main/verl/trainer/ppo/core_algos.py) 的 `AdvantageEstimator`);policy loss 通过 `actor_rollout_ref.actor.policy_loss.loss_mode` 切换(`vanilla`、`gspo`、`cispo`、`clip_cov` 等)。各算法的原理与配置说明见 verl 文档:[PPO](https://verl.readthedocs.io/en/latest/algo/ppo.html) / [GRPO](https://verl.readthedocs.io/en/latest/algo/grpo.html) / [DAPO](https://verl.readthedocs.io/en/latest/algo/dapo.html)。 + + +**使用 recipe 训练 Hy3**:训练流程级的完整算法(如 dynamic sampling 版 DAPO)在 verl [`recipe/`](https://github.com/verl-project/verl/tree/main/recipe) 目录下有独立实现,各自带启动脚本。将其模型指向 Hy3 时,须把以下 Hy3 必需配置带入 recipe 的启动脚本: + +```bash +# Hy3 必需配置(任何 recipe 通用) +actor_rollout_ref.model.path=/path/to/Hy3 +actor_rollout_ref.model.trust_remote_code=True +data.trust_remote_code=True +actor_rollout_ref.actor.megatron.use_mbridge=True +actor_rollout_ref.actor.megatron.vanilla_mbridge=False ++actor_rollout_ref.actor.megatron.override_transformer_config.moe_router_enable_expert_bias=True ++actor_rollout_ref.actor.megatron.override_transformer_config.moe_router_bias_update_rate=0 ++actor_rollout_ref.actor.megatron.override_transformer_config.moe_router_load_balancing_type=none ++actor_rollout_ref.actor.megatron.override_transformer_config.moe_grouped_gemm=True +``` + +自定义全新算法的扩展方式见 verl 文档 [Extend to other RL algorithms](https://verl.readthedocs.io/en/latest/advance/dpo_extension.html)。 + +## 实验结果 + +我们在 128 张 H20 GPU(16 机 × 8 卡)上启动了 Hy3 的 GRPO 训练(脚本:[`run_hy_v3_megatron.sh`](https://github.com/verl-project/verl/blob/main/examples/grpo_trainer/run_hy_v3_megatron.sh)):数学推理任务(DAPO 数据集),max response length 8192,采用 PP/CP/EP 多维并行 + 全量 offload,BF16 rollout + BF16 训练。 + +本次实验的具体取值:batch 128 prompts × 16 samples(2048 条轨迹/步)、`ppo_mini_batch_size=128`(每步单次更新)、lr 1e-6、clip 0.2/0.28(dual-clip c=10.0)、KL-free、DAPO overlong buffer(len 4096 / penalty 1.0)、IcePop 阈值 `0.5_4.0`、采样 temperature 0.9 / top_p 1.0。 + +训练动态稳定:rollout 与训练侧的 log-prob 偏差(`rollout_probs_diff`)全程基本小于 0.015,reward 与 AIME 验证集分数在整个训练过程中稳步上升。 + +![Hy3 RL 训练曲线](../assets/rl-training.png) + +## 致谢 + +感谢腾讯混元(Hunyuan)团队在模型训练与工程落地上的支持,以及 [verl](https://github.com/volcengine/verl)、[Megatron-Bridge](https://github.com/NVIDIA-NeMo/Megatron-Bridge)、[Megatron-LM](https://github.com/NVIDIA/Megatron-LM)、[vLLM](https://github.com/vllm-project/vllm) 社区的帮助。 diff --git a/utils.py b/utils.py new file mode 100644 index 00000000..571c7a6a --- /dev/null +++ b/utils.py @@ -0,0 +1,34 @@ +"""Hy3 API 示例公共工具""" + +import os +from openai import OpenAI +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + +# 配置信息 +BASE_URL = os.getenv("HY3_BASE_URL", "http://127.0.0.1:8000/v1") +API_KEY = os.getenv("HY3_API_KEY", "EMPTY") +MODEL = os.getenv("HY3_MODEL", "hy3") + + +def get_client(): + """创建 OpenAI 客户端""" + return OpenAI( + base_url=BASE_URL, + api_key=API_KEY, + timeout=60.0 + ) + + +def print_response(title, response): + """格式化打印响应""" + message = response.choices[0].message + print(f"\n{'='*50}") + print(f"标题: {title}") + print(f"{'='*50}") + print(f"回复: {message.content}") + print(f"结束原因: {response.choices[0].finish_reason}") + print(f"Token 用量: {response.usage}") + print(f"{'='*50}\n") \ No newline at end of file diff --git a/utils.py.txt b/utils.py.txt new file mode 100644 index 00000000..6bcae0af --- /dev/null +++ b/utils.py.txt @@ -0,0 +1,34 @@ +"""Hy3 API 示例公共工具""" + +import os +from openai import OpenAI +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + +# 配置信息 +BASE_URL = os.getenv("HY3_BASE_URL", "http://127.0.0.1:8000/v1") +API_KEY = os.getenv("HY3_API_KEY", "EMPTY") +MODEL = os.getenv("HY3_MODEL", "hy3") + + +def get_client(): + """创建 OpenAI 客户端""" + return OpenAI( + base_url=BASE_URL, + api_key=API_KEY, + timeout=60.0 + ) + + +def print_response(title, response): + """格式化打印响应""" + message = response.choices[0].message + print(f"\n{'='*50}") + print(f"📝 {title}") + print(f"{'='*50}") + print(f"💬 回复: {message.content}") + print(f"✅ 结束原因: {response.choices[0].finish_reason}") + print(f"📊 Token 用量: {response.usage}") + print(f"{'='*50}\n") \ No newline at end of file