news 2026/9/10 11:51:52

用 SGLang benchmark/uno 复现 UNO 全量数学评测:run_math_eval 实战与源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 SGLang benchmark/uno 复现 UNO 全量数学评测:run_math_eval 实战与源码解析

用 SGLang benchmark/uno 复现 UNO 全量数学评测:run_math_eval 实战与源码解析

【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang

本篇指南以 benchmark/uno/README.md 为核心,完整讲解 SGLang 仓库中全量数学评测工具run_math_eval.py的用法与实现:它如何在同一套数据集、提示词、采样参数与评分规则下,公平对比自回归(AR)解码与 UNO、DFLASH、EAGLE、EAGLE3 等投机解码算法的数学准确率与吞吐指标(TPF、tok/s)。读完本文,你将能够在自己的 H200 单卡环境上逐行复现 PR 中的评测表,理解 UNO 线性/树两种模式的参数映射、TPF 统计口径,以及 SGLang 侧 UNO 实现的约束边界。

工具定位:统一的投机解码数学评测框架

benchmark/uno/run_math_eval.py的核心设计目标是在完全一致的条件下横向比较不同解码算法。根据 README 与 run_math_eval.py 的实现,该评测框架具备以下特征:

  • 统一评测口径:AR、UNO、DFLASH、EAGLE、EAGLE3 使用相同的数据集、提示词、采样参数和评分器,只通过--speculative-algorithm切换解码路径。
  • 进程内引擎:创建进程内的sgl.Engine,不启动独立 server 进程。从源码可见,评测脚本在 _generate 中直接调用engine.generate(input_ids=..., sampling_params=..., rid=...),结束后在finally中调用engine.shutdown()
  • 计时边界明确:引擎启动耗时不计入计时区间(perf_counter()sgl.Engine构造之后才启动),并且不做任何额外的请求预热(warmup)
  • 数据集固定版本:下载固定 revision(commit hash)的 GSM8K、MATH-500、AIME 2024、AIME 2025、AIME 2026,保证跨机器、跨时间可复现。
  • 统一提示与模板:对所有引擎施加相同的\boxed{}作答指令与 Qwen reasoning 聊天模板。
  • 统一评分:基于math_verify的 parse/verify 进行数学答案比对。

环境准备与安装

README 要求以可编辑模式安装带评测依赖的 SGLang:

pip install -e "python[test]"

[test]可选依赖中包含了评测运行所需的math_verify等库。math_grader.py 在导入math_verify失败时会优雅降级(parse = Noneverify = None),以保证--help仍可用;但真正执行评分时会抛出RuntimeError("math scoring requires the 'math_verify' package"),提示必须安装该包。

数据集、提示词与模板:math_data.py 深度解读

数据集准备逻辑集中在 math_data.py。它定义了五个基准及其固定的 revision:

基准名数据集与 split固定 revision期望行数提示词构造
gsm8kopenai/gsm8k(main, test)740312add88f781978c0658806c59bc2815b98661319Q: {question}\nA: Let's think step by step.
math500HuggingFaceH4/MATH-500(test)6e4ed1a2a79af7d8630a6b768ec859cb5af4d3be500直接使用problem字段
aime24hypaai/Hypa_AIME2024(english)11ab79f0eed5f4fdf3d469b466663ab86bbd77c830Question: {problem}\nAnswer:
aime25math-ai/aime25(test)563bb8404243c5f09de6ec262f2db674fe5bce9b30Question: {problem}\nAnswer:
aime26math-ai/aime26(test)79037aebdb6580008fb960d17cb21fd3099083e330Question: {problem}\nAnswer:

所有基准共享同一条作答指令(math_data.py):

Please reason step by step and put your final answer in \boxed{}.

同时所有基准都使用chat_template_kwargs={"reasoning_effort": "high"}调用 Qwen reasoning 聊天模板(Qwen3 系列的 reasoning 模式)。这条指令会以 system 消息形式注入,若消息中已存在 system 消息则拼接到其前部(见 _format_prompt)。

prepare_benchmark_data的缓存与校验机制值得一提:数据以{benchmark}.jsonl落在--data-root下;若文件已存在且行数与期望一致则直接复用,否则重建;行数不符会抛ValueError;写入采用"临时文件 + replace"的原子方式,避免半成品污染缓存。

复现 H200 评测表:完整命令逐行解读

README 的核心是可复现脚本。首先设置环境变量与公共参数(均从仓库根目录执行):

export MODEL_PATH=Qwen/Qwen3-8B export TOKENIZER_PATH=Qwen/Qwen3-8B export UNO_LORA_PATH=s-sahoo/uno-qwen3-8B export DATA_ROOT=/path/to/math-eval-data export RESULT_ROOT=/path/to/math-eval-results COMMON_ARGS=( --model-path "$MODEL_PATH" --tokenizer-path "$TOKENIZER_PATH" --data-root "$DATA_ROOT" --context-length 40960 --max-tokens 32768 --temperature 1 --top-k 50 --top-p 0.95 --random-seed 42 )

COMMON_ARGS中的数值正是 run_math_eval.py 的默认值(CONTEXT_LENGTH = 40960MAX_TOKENS = 2**15 = 32768)以及采样默认值(temperature=1.0、top-k=50、top-p=0.95、random-seed=42)。PYTHONPATH=python前缀保证从仓库根目录能导入benchmark.uno包。

定义三个启动函数:AR 基线、UNO 线性模式、UNO 树模式。

run_ar() { local benchmark=$1 samples=$2 requests=$3 output_name=$4 PYTHONPATH=python python -m benchmark.uno.run_math_eval \ "${COMMON_ARGS[@]}" \ --benchmark "$benchmark" \ --num-samples "$samples" \ --max-running-requests "$requests" \ --output-dir "$RESULT_ROOT/$output_name" } run_linear_uno() { local benchmark=$1 samples=$2 requests=$3 output_name=$4 PYTHONPATH=python python -m benchmark.uno.run_math_eval \ "${COMMON_ARGS[@]}" \ --benchmark "$benchmark" \ --num-samples "$samples" \ --max-running-requests "$requests" \ --output-dir "$RESULT_ROOT/$output_name" \ --speculative-algorithm UNO \ --uno-lora-path "$UNO_LORA_PATH" \ --speculative-num-steps 1 \ --speculative-eagle-topk 1 \ --speculative-num-draft-tokens 8 } run_tree_uno() { local benchmark=$1 samples=$2 requests=$3 output_name=$4 PYTHONPATH=python python -m benchmark.uno.run_math_eval \ "${COMMON_ARGS[@]}" \ --benchmark "$benchmark" \ --num-samples "$samples" \ --max-running-requests "$requests" \ --output-dir "$RESULT_ROOT/$output_name" \ --speculative-algorithm UNO \ --uno-lora-path "$UNO_LORA_PATH" \ --speculative-num-steps 15 \ --speculative-eagle-topk 32 \ --speculative-num-draft-tokens 32 }

批次 64 的 AR 与 UNO 线性(B/K/V = 8/1/8)六行(GSM8K、MATH-500 每题 1 个样本;AIME 2025 每题 10 个样本,共 300 条 completion):

run_ar gsm8k 1 64 ar-gsm8k-c64 run_linear_uno gsm8k 1 64 uno-linear-b8-k1-v8-gsm8k-c64 run_ar math500 1 64 ar-math500-c64 run_linear_uno math500 1 64 uno-linear-b8-k1-v8-math500-c64 run_ar aime25 10 64 ar-aime25-c64 run_linear_uno aime25 10 64 uno-linear-b8-k1-v8-aime25-c64

批次 1 的 AR 与 UNO 树(B/K/V = 16/32/32)六行

run_ar gsm8k 1 1 ar-gsm8k-c1 run_tree_uno gsm8k 1 1 uno-tree-b16-k32-v32-gsm8k-c1 run_ar math500 1 1 ar-math500-c1 run_tree_uno math500 1 1 uno-tree-b16-k32-v32-math500-c1 run_ar aime25 10 1 ar-aime25-c1 run_tree_uno aime25 10 1 uno-tree-b16-k32-v32-aime25-c1

注意 UNO 树模式的参数映射:--speculative-num-steps 15意味着草稿前向宽度B = 15 + 1 = 16--speculative-eagle-topk 32即树宽K = 32--speculative-num-draft-tokens 32即验证宽度V = 32(详见下文"B/K/V 参数映射")。

run_math_eval 全参数详解

完整的命令行参数定义见 parse_args:

参数默认值说明
--model-pathQwen/Qwen3-8B目标模型路径或 HF repo id
--tokenizer-path缺省取--model-pathtokenizer 路径,支持独立指定
--revision目标模型权重 revision
--dtypebfloat16模型精度
--attention-backendfa3注意力后端(UNO 强制要求 fa3)
--data-root必填数据集缓存/下载目录
--output-dir必填结果输出目录
--benchmark全部五个可重复指定,如--benchmark gsm8k --benchmark math500;缺省跑全部
--limit每个基准最多执行的题目数
--num-samples1每题采样次数(AIME 表复现用 10)
--max-running-requests4引擎并发上限(表复现用 64 或 1)
--context-length40960上下文长度
--max-tokens32768每题最大生成 token 数
--temperature1.0采样温度
--top-k50top-k 采样
--top-p0.95nucleus 采样
--random-seed42随机种子
--speculative-algorithmEAGLE/EAGLE3/DFLASH/UNO(自动转大写)
--speculative-draft-model-path草稿模型路径(UNO 禁止使用)
--speculative-draft-model-revision草稿模型 revision
--speculative-num-steps草稿步数
--speculative-eagle-topkEAGLE 系候选数
--speculative-num-draft-tokens草稿/验证 token 数
--speculative-dflash-block-sizeDFLASH 专用:验证窗口长度
--speculative-draft-attention-backend草稿注意力后端
--uno-lora-pathUNO 专用:草稿 LoRA 检查点路径

参数校验(_validate_args):num_samplesmax_running_requestscontext_lengthmax_tokenstop_k必须为正数;top_p必须落在(0, 1]temperature必须非负;limit若指定必须为正。

上下文预留(context reserve)(_context_reserve)是一个容易踩坑的细节:投机解码的草稿/验证会额外占用上下文位置,脚本会按算法预留:DFLASH 预留2 * block_size;UNO 树模式(topk > 1)预留max(draft_tokens, steps+1) + 1;其余按draft_tokens预留。随后逐条检查context_length - prompt_len - reserve >= max_tokens,不满足即报错并提示"increase--context-length"。这解释了为什么表复现必须把 context length 开到 40960、max-tokens 开到 32768——既要容纳超长 reasoning 输出,又要给投机窗口留出余量。

运行流程与结果产物

main 的执行管线为:

  1. 解析参数并校验
  2. 加载 tokenizer:使用transformers.AutoTokenizeruse_fast=Truetrust_remote_code=True);
  3. 准备提示词:对每个基准下载/复用数据 → 逐行套用统一指令与 Qwen reasoning 模板渲染(_format_prompt返回 token ids 与文本)→ 按--num-samples复制出{source}:sample{i}的 prompt id;
  4. 构造引擎并生成_engine_options中设置了skip_tokenizer_init=True(tokenizer 已由脚本预加载)、log_level="info"max_running_requests,并将所有投机相关选项原样透传给引擎(run_math_eval.py);_generate在引擎构造完成后才开始计时;
  5. 统计 TPF:见下节;
  6. 写结果:按基准分目录输出。

每个基准的--output-dir/<benchmark>/下会生成四类文件:

文件内容
generations.jsonl原始生成结果(含output_ids、解码文本、token 数、num_forwardstokens_per_forwardsglang_meta_info
grades.jsonl逐条评分后的结果(含parsed_generationscorrectaccuracy
scores.json评分汇总(num_rowsnum_problemsaccuracy、单样本的avg_at_1/pass_at_1,多样本的pass_at_N
summary.json/summary.md跨基准汇总

summary.md的表格格式固定为(run_math_eval.py):

| Dataset | Accuracy | TPF | tok/s | tok/s/request |

末行Average的 Accuracy 与 TPF 是各数据集的无加权平均;tok/s 是总输出 token 数除以计时生成秒数;tok/s/request 再除以max_running_requestssummary.json则记录了完整元数据(engine=sglang-offline、mode、模型路径、投机参数、采样参数、generation_secondsnum_tokensnum_forwardstokens_per_forwardunweighted_mean_tokens_per_forwardby_benchmark等),并在进程结束前打印到 stdout。

TPF 统计口径:三种算法的差异

TPF(tokens per forward,每次前向生成的 token 数)是衡量投机解码效率的关键指标,脚本在 _build_rows 中按算法区别统计:

  • AR 基线num_forwards = len(token_ids),即每生成一个 token 记一次前向,TPF 恒为 1;
  • UNOnum_forwards = 2 * spec_verify_ct。因为 UNO 每个投机周期的两次前向都是完整的目标模型前向——扩散路径(diffusion-pathway)的草稿前向 + AR 路径(AR-pathway)的验证前向,两者各算一次;
  • EAGLE / EAGLE3 / DFLASHnum_forwards = spec_verify_ct,遵循 SGLang 的 acceptance-length 约定,只统计目标模型验证前向(草稿模型的前向不计入)。

spec_verify_ct来自引擎输出的meta_info(OpenAI 协议中对应的字段定义见 protocol.py)。需要特别留意:由于口径不同,UNO 的 TPF 与 EAGLE/DFLASH 的 TPF不可直接数值对比,只能在同一算法内部比较配置优劣。

评分器实现:math_grader.py

评分模块 math_grader.py 自称"Minimal math scorer adapted from Nano-vLLM-UNO's Eval360 grader"。其核心处理链路:

  1. 答案抽取extract_last_boxed_content用正则 + 花括号配对深度扫描提取最后一个\boxed{...}/\fbox{...}的内容;
  2. 文本规范化normalize_answer_text做大量 LaTeX 清洗——提取 GSM8K 的####后答案、\dfrac/\tfrac归一为\frac、去除\left/\right\mathbf\text/\mathrm包装、\frac{a}{b}a/b、去掉空白与千分位逗号等;
  3. 候选解析_parse_boxed_content优先交给math_verify.parse(带$包裹尝试),纯变量答案保留文本,数字答案提取前导数字;无 boxed 时走_parse_unboxed_answer的十余种自然语言模式("The answer is ..."、"Final answer: ..."、"Therefore, the answer is ..." 等),最后兜底取最后一个句子的最后一个数字;
  4. 比对_compare_answers优先math_verify.verify(answer, gold),失败则再解析 gold 反向 verify;文本型答案走_text_answers_match(含浮点相等、(A)括号选项、向量分量比对)。

score_math同时输出多套口径:avg_at_1/pass_at_1(首样本),以及多样本时的avg_at_N/pass_at_N(任一采样正确即算通过)。AIME 2025 的 10 采样配置正是通过num_samples=10触发了后一套统计。

评测其他投机解码器:DFLASH 与 EAGLE/EAGLE3

README 强调:"The runner uses the same public option names assglang serve",即评测脚本与在线服务共享同一套投机参数命名,结论可直接迁移到线上部署。

DFLASH 示例(草稿模型 + block size + 草稿注意力后端):

PYTHONPATH=python python -m benchmark.uno.run_math_eval \ "${COMMON_ARGS[@]}" \ --benchmark math500 \ --num-samples 1 \ --output-dir "$RESULT_ROOT/dflash-b8-math500-c64" \ --max-running-requests 64 \ --speculative-algorithm DFLASH \ --speculative-draft-model-path z-lab/Qwen3-8B-DFlash-b16 \ --speculative-dflash-block-size 8 \ --speculative-draft-attention-backend fa3

EAGLE / EAGLE3 示例:

export EAGLE_DRAFT_MODEL=/path/to/compatible-eagle-draft-model PYTHONPATH=python python -m benchmark.uno.run_math_eval \ "${COMMON_ARGS[@]}" \ --benchmark math500 \ --num-samples 1 \ --output-dir "$RESULT_ROOT/eagle3-b8-math500-c64" \ --max-running-requests 64 \ --speculative-algorithm EAGLE3 \ --speculative-draft-model-path "$EAGLE_DRAFT_MODEL" \ --speculative-num-steps 7 \ --speculative-eagle-topk 1 \ --speculative-num-draft-tokens 8

EAGLE 与 DFLASH 的 TPF 遵循上文所述的 acceptance-length 约定。

UNO 原理与源码印证

单模型双前向架构

SGLang 中的 UNO 实现是 UnoWorkerV2("Single-model UNO worker with linear and native-EAGLE tree decode")。与 EAGLE/DFLASH 需要加载独立草稿模型不同,UNO复用目标 transformer 完成每个投机周期的两次前向

  • 草稿前向:每个请求的第一行使用基础权重,其余B - 1行使用训练好的 UNO LoRA("diffusion-pathway draft");
  • 验证前向:完全使用基础权重("AR-pathway verification")。

因此 spec.py 中 UNO 的启动参数是--speculative-algorithm UNO+--uno-lora-path(如s-sahoo/uno-qwen3-8B),而--speculative-draft-model-path会被显式拒绝。这也解释了为什么 UNO 的每次周期成本是两次完整目标模型前向——评测脚本中2 * spec_verify_ct的 TPF 口径正是对该架构的忠实刻画。

B/K/V 参数映射

UNO 的三种形态参数B/K/V含义为:草稿前向宽度 / 每层扩展保留的候选数 / 验证宽度。命令行映射由 uno_worker_v2.py 与 speculative_hook.py 共同决定:

模式判定BKV
线性(Linear)--speculative-eagle-topk ≤ 1--speculative-num-draft-tokens(如 8)1(steps/topk 被强制为 1)--speculative-num-draft-tokens(8)
树(Tree)--speculative-eagle-topk > 1--speculative-num-steps + 1(如 15+1=16)--speculative-eagle-topk(32)--speculative-num-draft-tokens(32)

线性模式构建并验证单条草稿链(类似 DFLASH 的 proposal 布局);树模式在每层草稿深度扩展多个候选,并复用 SGLang 的 EAGLE 树验证路径(native-EAGLE tree verification)。注意 UNO 的K控制的是提议树宽度,与请求采样参数top_k无关。

启动期硬性约束

_handle_uno 是 UNO 配置的"守门员",任何违规配置都会在启动期直接抛ValueError,而不是拖到解码时才崩溃:

  • 仅支持 CUDA,且要求 prefill 与 decode 注意力后端均为FA3
  • 必须提供--uno-lora-path,禁止--speculative-draft-model-path
  • 要求TP=PP=1,不支持 DP attention 与上下文并行(CP);
  • 不支持确定性推理(--enable-deterministic-inference)、strict thinking(依赖 grammar decoding)、公共 Multi-LoRA 服务(--uno-lora-path加载的是固定内部 adapter);
  • 不使用--speculative-use-rejection-sampling(UNO 自管随机验证);
  • 树模式额外校验:V ≥ BV ≤ 128V × K ≤ 2048、提议树容量与 EAGLE parent-list ABI 可表示性、两个接受阈值必须为 1.0、暂不支持 PDMux 与 two-batch overlap;
  • 混合分块 prefill(mixed chunked prefill)会被自动禁用(给出 warning 而非报错)。

以上约束在 speculative_decoding.mdx 的 "Key requirements and limitations" 一节亦有完整列举,可作为部署前核对清单。

测试验证

仓库内的测试进一步印证了实现细节:

  • test_uno.py 是端到端 CUDA-graph 测试:在Qwen/Qwen3-8B+s-sahoo/uno-qwen3-8B上同时覆盖线性与树两种模式,用 32 token 的贪心前缀校验与 AR 解码的输出一致性(PARITY_TOKENS = 32),并显式定义FORWARDS_PER_UNO_CYCLE = 2——与 TPF 双前向口径完全对应;
  • test_uno_tree_config.py 验证了启动期校验(确定性推理、strict thinking、非法树形配置均被拒绝);
  • 另有 test_uno_request_validation.py、test_uno_lora_targets.py 覆盖请求级校验与 UNO LoRA 目标层校验(不支持的 LoRA 目标层会在启动期被拒绝)。

常见问题与注意事项

  • 从仓库根目录运行:命令均以PYTHONPATH=python python -m benchmark.uno.run_math_eval形式执行,不要在benchmark/uno/目录内直接python run_math_eval.py
  • --data-root--output-dir必须显式指定,且应使用不同目录,避免数据缓存与结果互相覆盖。
  • 上下文余量不足:若某题的可用生成空间小于--max-tokens,脚本会报错并提示增大--context-length;投机解码会额外占用上下文(见_context_reserve)。
  • TPF 跨算法不可直接比较:UNO 计两次完整前向,EAGLE/DFLASH 只计验证前向,AR 恒为 1;比较时应关注同一算法内的相对提升,或同时参考 tok/s 与准确率。
  • UNO 适配器必须与目标模型检查点精确匹配:README 与官方文档均强调使用为特定 checkpoint 训练的 adapter(当前已验证Qwen/Qwen3-8B),LoRA 目标层不匹配会在启动期报错。
  • 评分依赖math_verify:未安装时 CLI 帮助可用,但评分会报错;请使用pip install -e "python[test]"安装完整评测依赖。

【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 11:51:05

锂电池仿真:单RC等效电路模型与参数辨识实践解析

简介&#xff1a;这是一份基于MATLAB/Simulink环境的锂电池仿真学习资源&#xff0c;面向电池建模、状态估计、参数辨识以及电池管理系统相关方向的工程师和高校学生。压缩包共19个文件&#xff0c;以slx仿真模型、m初始化脚本、mat数据文件和png/jpg结果图片为主&#xff0c;整…

作者头像 李华
网站建设 2026/9/10 11:50:57

GE图引擎CreateBoolTensor API文档

CreateBoolTensor 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFl…

作者头像 李华
网站建设 2026/9/10 11:50:05

SpringBoot马拉松赛事管理系统开发实践

1. 项目概述马拉松赛事管理系统是基于SpringBoot框架开发的赛事管理平台&#xff0c;主要解决赛事报名、成绩统计、选手管理等核心业务场景。这个系统特别适合中小型马拉松赛事组委会使用&#xff0c;能够有效降低人工管理成本&#xff0c;提升赛事运营效率。我在实际开发中发现…

作者头像 李华
网站建设 2026/9/10 11:46:57

MySQL视图:原理、创建与性能优化实战

1. 视图的本质与核心价值MySQL视图本质上是一个虚拟表&#xff0c;其内容由查询定义。与物理表不同&#xff0c;视图不存储实际数据&#xff0c;而是通过保存的SQL查询语句动态生成结果集。这个特性带来了几个独特优势&#xff1a;逻辑抽象层&#xff1a;视图可以隐藏底层表的复…

作者头像 李华
网站建设 2026/9/10 11:46:42

中文票据OCR实战:OpenCV预处理+tesseract字段提取

简介&#xff1a;本资源是一套基于PythonOpenCVtesseract实现的中文扫描票据OCR识别完整项目&#xff0c;面向计算机、软件工程、人工智能等专业的本科生及课程设计/毕业设计实践者&#xff0c;解决纸质票据图像预处理、文字定位与高准确率中文识别等典型CV应用问题。压缩包共1…

作者头像 李华