<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>第九章 可观测性与调试 on Machine Learning 学习笔记</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part4-debugging-maintenance/observability-and-debugging/</link><description>Recent content in 第九章 可观测性与调试 on Machine Learning 学习笔记</description><generator>Hugo</generator><language>en</language><atom:link href="https://kingye.me/study-ml/docs/book/sglang-internals/part4-debugging-maintenance/observability-and-debugging/index.xml" rel="self" type="application/rss+xml"/><item><title>9.1 request logger、trace 与 time stats</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part4-debugging-maintenance/observability-and-debugging/request-logger-trace-and-time-stats/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part4-debugging-maintenance/observability-and-debugging/request-logger-trace-and-time-stats/</guid><description>&lt;h1 id="request-loggertrace-与-time-stats"&gt;request logger、trace 与 time stats&lt;a class="anchor" href="#request-loggertrace-%e4%b8%8e-time-stats"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;这一节会把最常见的观测入口先统一起来，解释 request logger、trace 和时间字段分别站在哪一层、解决什么问题。&lt;/p&gt;
&lt;h2 id="为什么这三样要放在一起看"&gt;为什么这三样要放在一起看&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%99%e4%b8%89%e6%a0%b7%e8%a6%81%e6%94%be%e5%9c%a8%e4%b8%80%e8%b5%b7%e7%9c%8b"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;很多人会把 request logger、trace 和时间字段当成三组并列工具：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;logger 负责打印&lt;/li&gt;
&lt;li&gt;trace 负责看链路&lt;/li&gt;
&lt;li&gt;time stats 负责算延迟&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但对 SGLang 来说，这三者更像同一条证据链的三个切面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;request logger 提供请求首尾事实&lt;/li&gt;
&lt;li&gt;trace 提供阶段切片&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ReqTimeStats&lt;/code&gt; 提供统一时间语义&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果把它们分开理解，第四部分后面的很多调试动作都会重新变得零散。&lt;/p&gt;
&lt;h2 id="request-logger-真正留下了什么"&gt;request logger 真正留下了什么&lt;a class="anchor" href="#request-logger-%e7%9c%9f%e6%ad%a3%e7%95%99%e4%b8%8b%e4%ba%86%e4%bb%80%e4%b9%88"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;RequestLogger&lt;/code&gt; 在请求首尾两端留下结构化事实。典型的日志行如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;INFO: rid=7a3c21f8 | input=128 toks | output=64 toks | finish_reason=stop |
 TTFT=0.342s | E2E=2.187s | throughput=29.2 tok/s&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这一行日志来自 &lt;code&gt;python/sglang/srt/utils/request_logger.py&lt;/code&gt;，在请求结束时写入。它提供的不是 trace 级细节，而是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rid&lt;/code&gt;：请求的全局唯一标识，用于跨层对照；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input&lt;/code&gt; / &lt;code&gt;output&lt;/code&gt; token 数：最基础的输入输出规模；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;finish_reason&lt;/code&gt;：请求是正常结束（&lt;code&gt;stop&lt;/code&gt;）、达到长度上限（&lt;code&gt;length&lt;/code&gt;），还是中途中止（&lt;code&gt;abort&lt;/code&gt;）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TTFT&lt;/code&gt;：time to first token，衡量首包延迟；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;E2E&lt;/code&gt;：请求从到达到最后一个 token 送出的全程耗时。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对排障来说，request logger 的价值不在于它有多详细，而在于它把&lt;strong&gt;首尾事实&lt;/strong&gt;钉住了。在用 trace 或 metrics 深入之前，先确认这一行是否存在、&lt;code&gt;finish_reason&lt;/code&gt; 是否符合预期，能排掉一大类&amp;quot;请求是不是根本没完成&amp;quot;这类问题。&lt;/p&gt;</description></item><item><title>9.2 metrics、profiling 与 evidence</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part4-debugging-maintenance/observability-and-debugging/metrics-profiling-and-evidence/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part4-debugging-maintenance/observability-and-debugging/metrics-profiling-and-evidence/</guid><description>&lt;h1 id="metricsprofiling-与-evidence"&gt;metrics、profiling 与 evidence&lt;a class="anchor" href="#metricsprofiling-%e4%b8%8e-evidence"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;这一节会解释 metrics、profiling 和更硬的证据面怎样彼此配合，避免把&amp;quot;有指标&amp;quot;误读成&amp;quot;已经能定位问题&amp;quot;。&lt;/p&gt;
&lt;h2 id="metrics-和-profiling-为什么不能混看"&gt;metrics 和 profiling 为什么不能混看&lt;a class="anchor" href="#metrics-%e5%92%8c-profiling-%e4%b8%ba%e4%bb%80%e4%b9%88%e4%b8%8d%e8%83%bd%e6%b7%b7%e7%9c%8b"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;metrics 更适合回答&amp;quot;系统整体现在怎么样&amp;quot;，profiling 更适合回答&amp;quot;这一段为什么慢&amp;quot;。这两者都属于证据，但粒度和代价完全不同。&lt;/p&gt;
&lt;p&gt;如果把它们混看，最常见的后果就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只看到整体吞吐变化，却不知道是哪个阶段变慢了；&lt;/li&gt;
&lt;li&gt;或者 profile 看得太细，却没有先确认问题到底是不是整体趋势。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="哪些-metrics-值得先看"&gt;哪些 metrics 值得先看&lt;a class="anchor" href="#%e5%93%aa%e4%ba%9b-metrics-%e5%80%bc%e5%be%97%e5%85%88%e7%9c%8b"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SGLang 在 &lt;code&gt;/metrics&lt;/code&gt; 路径（Prometheus 格式）暴露系统指标。以下是排障中最先需要盯的几个：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;请求状态类&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sglang:num_running_reqs # 当前正在执行（在 forward pass 里）的请求数
sglang:num_waiting_reqs # 在 waiting queue 里等待的请求数
sglang:num_queue_reqs # 包含 grammar queue 的等待请求总数&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这三个数字放在一起看，能立刻区分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;num_running&lt;/code&gt; 低但 &lt;code&gt;num_waiting&lt;/code&gt; 高 → scheduler admission 有瓶颈，或 batch 成不起来&lt;/li&gt;
&lt;li&gt;&lt;code&gt;num_running&lt;/code&gt; 饱和，&lt;code&gt;num_waiting&lt;/code&gt; 也高 → 正在正常工作，只是负载大&lt;/li&gt;
&lt;li&gt;两者都低但延迟高 → 问题在单请求执行侧，不是吞吐侧&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;资源占用类&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sglang:token_usage # KV cache 占用率（0.0 ~ 1.0），接近 1.0 代表显存紧张
sglang:gpu_memory_usage_bytes # GPU 显存绝对用量
sglang:cache_hit_ratio # RadixCache 前缀命中率&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;token_usage&lt;/code&gt; 接近 1.0 时，scheduler 会开始更激进地驱逐 KV，&lt;code&gt;cache_hit_ratio&lt;/code&gt; 通常随之下降。这两个字段放在一起比单独看更有价值。&lt;/p&gt;</description></item><item><title>9.3 从症状到根因的调试路径</title><link>https://kingye.me/study-ml/docs/book/sglang-internals/part4-debugging-maintenance/observability-and-debugging/symptom-to-root-cause/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://kingye.me/study-ml/docs/book/sglang-internals/part4-debugging-maintenance/observability-and-debugging/symptom-to-root-cause/</guid><description>&lt;h1 id="从症状到根因的调试路径"&gt;从症状到根因的调试路径&lt;a class="anchor" href="#%e4%bb%8e%e7%97%87%e7%8a%b6%e5%88%b0%e6%a0%b9%e5%9b%a0%e7%9a%84%e8%b0%83%e8%af%95%e8%b7%af%e5%be%84"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;这一节会给出一条更像运行手册的调试路线：先看什么、再排什么、哪些证据能真正缩小问题边界。&lt;/p&gt;
&lt;h2 id="为什么这一节必须放在最后"&gt;为什么这一节必须放在最后&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%99%e4%b8%80%e8%8a%82%e5%bf%85%e9%a1%bb%e6%94%be%e5%9c%a8%e6%9c%80%e5%90%8e"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;前面的调试章节已经把 request logger、trace、metrics、profiling、测试和回归路径分别讲出来了，但如果没有一节把它们重新压成一条工作顺序，读者在现场仍然很容易知道&amp;quot;有哪些工具&amp;quot;，却不知道&amp;quot;第一步到底该做什么&amp;quot;。&lt;/p&gt;
&lt;p&gt;这一节的职责，就是把前面几节重新收束成三条从症状走到根因的具体路径。&lt;/p&gt;
&lt;h2 id="场景一请求高延迟或-p99-劣化"&gt;场景一：请求高延迟或 P99 劣化&lt;a class="anchor" href="#%e5%9c%ba%e6%99%af%e4%b8%80%e8%af%b7%e6%b1%82%e9%ab%98%e5%bb%b6%e8%bf%9f%e6%88%96-p99-%e5%8a%a3%e5%8c%96"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;症状&lt;/strong&gt;：&lt;code&gt;ttft_seconds&lt;/code&gt; 或 &lt;code&gt;e2e_req_latency_seconds&lt;/code&gt; 的 P99 升高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步&lt;/strong&gt;：拉 metrics 确认资源状态：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl http://localhost:30000/metrics | grep -E &lt;span style="color:#e6db74"&gt;&amp;#39;num_running|num_waiting|token_usage|cache_hit&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;根据数字判断属于哪类问题：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;现象&lt;/th&gt;
 &lt;th&gt;指向&lt;/th&gt;
 &lt;th&gt;下一步&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;num_waiting&lt;/code&gt; 远大于 &lt;code&gt;num_running&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;调度瓶颈&lt;/td&gt;
 &lt;td&gt;看 &lt;code&gt;token_usage&lt;/code&gt;：若 &amp;gt; 0.9，是 KV 压力导致 batch 受限&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;token_usage&lt;/code&gt; &amp;gt; 0.9，&lt;code&gt;cache_hit_ratio&lt;/code&gt; 下降&lt;/td&gt;
 &lt;td&gt;KV 资源紧张&lt;/td&gt;
 &lt;td&gt;看 waiting queue 里请求的平均长度；考虑调整 &lt;code&gt;--mem-fraction-static&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;num_running&lt;/code&gt; 饱和，&lt;code&gt;inter_token_latency&lt;/code&gt; 正常&lt;/td&gt;
 &lt;td&gt;正常高负载&lt;/td&gt;
 &lt;td&gt;关注 &lt;code&gt;gen_throughput&lt;/code&gt; 是否持续下降，考虑扩容&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;两者都低但 P99 高&lt;/td&gt;
 &lt;td&gt;单请求问题&lt;/td&gt;
 &lt;td&gt;进入第二步看 per-request trace&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;第二步&lt;/strong&gt;：找到高延迟的单个请求，通过 &lt;code&gt;rid&lt;/code&gt; 拿到它的 &lt;code&gt;ReqTimeStats&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;TTFT = 2.8s 但 prefill_start_time - tokenize_end_time = 2.3s&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这说明高延迟的 2.3 秒发生在 waiting queue 里，不是 prefill 本身慢。对应的修方向是调度侧，不是 GPU 侧。&lt;/p&gt;</description></item></channel></rss>