Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

## 2025-02-23 - Avoid dataclasses.asdict in Hot Paths
**Learning:** `dataclasses.asdict` does recursive deepcopy internally and is incredibly slow for large dataclasses or objects instantiated frequently. In FastDeploy, it was used in `RequestMetrics.to_dict()`, creating significant overhead.
**Action:** When defining `to_dict()` or custom serialization methods for fast/frequent dataclasses, avoid `asdict`. Instead, iterate through `self.__dataclass_fields__` with `getattr` and do shallow copying for basic types (`int`, `float`, `str`, `bool`, `type(None)`). For nested dataclasses, ensure they also implement their own `to_dict()` method to skip the `asdict` recursive penalty.
Comment on lines +2 to +4
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR 标题需要至少包含一个标签(模板要求形如 [Optimization] ...)。当前标题包含引号/emoji 且缺少方括号标签,建议改为例如 [Optimization] Optimize RequestMetrics & SpeculateMetrics Serialization(或选择更贴切的标签)。

Copilot uses AI. Check for mistakes.
21 changes: 20 additions & 1 deletion fastdeploy/engine/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,26 @@ def to_dict(self):
"""
Convert the RequestMetrics object to a dictionary.
"""
return {k: v for k, v in asdict(self).items()}
import dataclasses

res = {}
for k in self.__dataclass_fields__:
v = getattr(self, k)
Comment on lines +900 to +904
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RequestMetrics.to_dict() 位于高频路径中,这里在函数体内执行 import dataclasses 会在每次调用时产生额外的模块查找开销(即使有 import cache 仍会有字典查找成本)。建议将 import dataclasses(或 from dataclasses import asdict, is_dataclass)移动到模块级,避免在热路径里重复执行 import 语句。

Copilot uses AI. Check for mistakes.
if type(v) in (int, float, str, bool, type(None)):
res[k] = v
elif isinstance(v, list):
res[k] = list(v)
elif isinstance(v, dict):
res[k] = dict(v)
else:
if dataclasses.is_dataclass(v):
if hasattr(v, "to_dict"):
res[k] = v.to_dict()
else:
res[k] = dataclasses.asdict(v)
else:
res[k] = v
return res

def record_recv_first_token(self):
cur_time = time.time()
Expand Down
14 changes: 14 additions & 0 deletions fastdeploy/worker/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,20 @@ class SpeculateMetrics:
"""
accept_ratio_per_head: list[float]

def to_dict(self):
return {
"accepted_tokens": self.accepted_tokens,
"rejected_tokens": self.rejected_tokens,
"accept_ratio": self.accept_ratio,
"average_accept_length": self.average_accept_length,
"accepted_tokens_per_head": (
list(self.accepted_tokens_per_head) if self.accepted_tokens_per_head is not None else None
),
"accept_ratio_per_head": (
list(self.accept_ratio_per_head) if self.accept_ratio_per_head is not None else None
),
}


@dataclass
class SamplerOutput:
Expand Down
Loading