Profiling page: per-turn phase timings, live in the web dashboard
The engine already tracks where each turn's wall time goes (expert-disk service, I/O wait, expert matmul, attention, lm_head) — it just only spoke at exit or under PROF=1. Stream it instead: - glm.c: mux serve emits a per-turn "PROF" protocol line next to TIERS/HITS (window deltas per request, same convention as the STAT hit%); the phase window base is now always captured (a few loads per request). - openai_server.py: parses PROF into a 120-turn rolling window and serves it at /profile (read-only, same trust level as /health). - web: new Profiling tab — stat tiles (tok/s, wall, tokens/forward, disk service), wall-time composition bars for the last turn and the window, per-turn throughput and stacked phase columns with hover readouts, and a table of recent turns. Disk service is shown apart from the stack: it overlaps with compute, so only the I/O wait the compute thread felt counts inside wall time. Phase colours are a CVD-validated set with gaps + legend + table so identity never rides on colour alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WhTmF8yvEBgSkUKSVfZF7P
This commit is contained in:
committed by
Nicholas Beerbower
parent
63a6824881
commit
6afffbcbf2
+14
-1
@@ -1,6 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { extractSSE, getHealth, serverEndpoint, streamChat } from "./api"
|
||||
import { extractSSE, getHealth, getProfile, serverEndpoint, streamChat } from "./api"
|
||||
|
||||
afterEach(() => vi.unstubAllGlobals())
|
||||
|
||||
@@ -36,6 +36,19 @@ describe("runtime API", () => {
|
||||
headers: expect.objectContaining({ Authorization: "Bearer secret" }),
|
||||
}))
|
||||
})
|
||||
|
||||
it("requests the profiling history next to the OpenAI v1 prefix", async () => {
|
||||
const turn = {
|
||||
wall_s: 2.5, prompt_tokens: 7, completion_tokens: 12,
|
||||
expert_disk_s: 0.4, expert_wait_s: 0.1, expert_matmul_s: 0.9,
|
||||
attention_s: 0.6, lm_head_s: 0.2, forwards: 15,
|
||||
}
|
||||
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ seq: 1, turns: [turn] })))
|
||||
vi.stubGlobal("fetch", fetchMock)
|
||||
|
||||
await expect(getProfile("http://localhost:8000/v1/")).resolves.toEqual({ seq: 1, turns: [turn] })
|
||||
expect(fetchMock).toHaveBeenCalledWith("http://localhost:8000/profile", expect.anything())
|
||||
})
|
||||
})
|
||||
|
||||
describe("chat request extensions", () => {
|
||||
|
||||
@@ -49,6 +49,23 @@ export interface HealthResponse {
|
||||
hwinfo?: HwinfoHealth
|
||||
}
|
||||
|
||||
export interface ProfileTurn {
|
||||
wall_s: number
|
||||
prompt_tokens: number
|
||||
completion_tokens: number
|
||||
expert_disk_s: number
|
||||
expert_wait_s: number
|
||||
expert_matmul_s: number
|
||||
attention_s: number
|
||||
lm_head_s: number
|
||||
forwards: number
|
||||
}
|
||||
|
||||
export interface ProfileResponse {
|
||||
seq: number
|
||||
turns: ProfileTurn[]
|
||||
}
|
||||
|
||||
export interface TokenUsage {
|
||||
prompt_tokens: number
|
||||
completion_tokens: number
|
||||
@@ -100,6 +117,12 @@ export async function getHealth(baseUrl: string, apiKey = "", signal?: AbortSign
|
||||
return (await response.json()) as HealthResponse
|
||||
}
|
||||
|
||||
export async function getProfile(baseUrl: string, apiKey = "", signal?: AbortSignal): Promise<ProfileResponse> {
|
||||
const response = await fetch(serverEndpoint(baseUrl, "profile"), { headers: headers(apiKey), signal })
|
||||
if (!response.ok) throw new Error(await responseError(response))
|
||||
return (await response.json()) as ProfileResponse
|
||||
}
|
||||
|
||||
export function extractSSE(buffer: string) {
|
||||
const frames = buffer.split(/\r?\n\r?\n/)
|
||||
const rest = frames.pop() || ""
|
||||
|
||||
Reference in New Issue
Block a user