mindspore-ai / mindspore-ai/hyper-parallel
CC分析fully_shard/DTensor相关编码问题
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 53
- Forks
- 63
- Avg merge
- 23h 45m
- Merged PRs (30d)
- 63
Description
HyperParallel 框架深度分析报告
分析范围
对 fully_shard() 强相关的 DTensor 核心(Layout / Placement / DeviceMesh / Redistribution)、FSDP/HSDP 训练流水线(参数分片/聚合/梯度同步)以及分布式算子推导进行了全面审计。以下按 严重程度 分级汇报所有发现的问题。
一、致命级别 (Critical) — 会导致运行崩溃或静默产生错误结果
1. is_partial 未加括号调用,条件永远为 True
if self._apply_eazy_redistribute(from_layout, to_layout):
if from_layout.is_partial:
from_layout.reset_partial()
x = self._redistribution_without_shape(x, from_layout, to_layout, key)
Layout.is_partial 是一个 普通方法(非 @property):
def is_partial(self):
"""Return true if any dim in mesh_shape is partial"""
return any(self.partial)
不加 () 取到的是方法对象本身,Python 中任何方法对象都是 truthy。后果:
- 第 214 行:
reset_partial()永远被调用,即使没有 partial 状态。这会篡改from_layout(即源 DTensor 的 layout),可能导致共享同一 layout 对象的其他 DTensor 状态被破坏。 - 第 264 行同一文件:
not from_layout.is_partial永远为False,reduce_partial的 early-return 永远不触发,即使没有 partial 也会尝试执行 reduce。
2. self.platform 未定义 — ReduceScatter 路径必定崩溃
def _reduce_scatter_along_dev_dim_with_axis(self, x, axis, op, layout, dev_dim):
"""Do reduce_scatter at specified axis along dev_dim."""
dev_num = layout.mesh_shape[layout.alias_name.index(dev_dim)]
group = layout.get_comm_group_by_axis(dev_dim)
output_tensor = self.platform.reduce_scatter(x, dev_num, axis, op, group)
return output_tensor
TensorRedistribution 类没有 self.platform 属性;模块级变量是 platform。当重分布路径需要 ReduceScatter(Partial -> Shard 转换)时,会抛出 AttributeError。这意味着 Partial -> Shard 重分布功能完全不可用。
3. DeviceMesh 缓存 key 仅用首尾 rank — 不同拓扑可能返回错误 Mesh
rank_ids = (rank_list[0], rank_list[-1])
mesh_dim_names = tuple(mesh_dim_names)
map_key = hash((mesh_shape, mesh_dim_names, rank_ids))
只使用 rank_list 的第一个和最后一个 rank 作为缓存 key。两个 shape 和 dim_names 相同但内部 rank 排布不同的 mesh(如 (0,2,1,3) vs (0,1,2,3))会命中同一缓存。后果:返回错误的 DeviceMesh 对象,通信分组错误,数据放置错误。同样的缺陷存在于 to_hash() 方法中,会进一步传播到 redistribution 的缓存 key。
4. post_backward 中 reduced_grad 可能未定义 — HSDP 场景崩溃
if hsdp_param.shard_world_size > 1:
if hsdp_param.unsharded_param.grad is None:
continue
reduced_grad, _ = hsdp_param.reduce_scatter_grad(...)
if self.requires_all_reduce and hsdp_param.replicate_world_size > 1:
...
reduced_grad, _ = hsdp_param.all_reduce_grad(
grad=reduced_grad, # <-- 可能 NameError
...
)
当参数 shard_world_size == 1(未分片)但 replicate_world_size > 1(有复制)时,if shard_world_size > 1 分支被跳过,reduced_grad 从未赋值。下方 all_reduce_grad(grad=reduced_grad) 将抛出 UnboundLocalError。这个场景在 HSDP(2D mesh,部分维度复制、部分维度分片)下完全有可能出现。
5. set_requires_all_reduce 设置了错误的属性名
def set_requires_all_reduce(self, requires_all_reduce: bool):
if self.hsdp_state is not None:
self.hsdp_state.all_reduce_grads = requires_all_reduce
实际使用的属性名是 self.requires_all_reduce(在 TorchHSDPStateV2.__init__ 第 51 行设置),但此 setter 写的是 all_reduce_grads,完全是一个新属性。后果:用户调用 set_requires_all_reduce(False) 后静默无效,梯度的 AllReduce 仍然会执行。
二、严重级别 (High) — 可能导致精度问题或特定场景下的错误
6. RNG offset 计算使用了 mesh shape 而非 tensor shape
def _set_post_op_offset(self, state, device_mesh, old_offset):
dtensor_shape = device_mesh.mesh_shape # BUG
numel = functools.reduce(operator.mul, dtensor_shape, 1)
应该使用分布式张量的全局 shape 而非设备网格 shape。这会导致各 rank 的 RNG offset 推进量错误,破坏随机数的确定性重现保证,进而导致 dropout 等操作在不同 rank 上不一致。
7. need_synchronize 变量作用域 Bug — 仅反映最后一个参数
need_synchronize = self._apply_reduced_grad(hsdp_param, reduced_grad)
if need_synchronize:
need_synchronize 在 for 循环内赋值,但检查在循环外。只有最后一个参数的返回值生效。如果前面的参数需要同步(如 CPU offload)但最后一个不需要,同步会被跳过,导致 CPU offload 场景下的数据竞争。
8. Layout _infer_slice_area_by_rank 不处理不均匀分片
slice_size = full_shape[axis] // split_num
start = slice_id * slice_size
整数除法直接截断,不均匀分片时(如 shape=7 在 2 设备上分片),最后的元素被静默丢弃。PyTorch DTensor 会处理 remainder shard,这里缺失。
9. Norm 算子的输出 layout 可能错误
output_map = x_layout.alias_tensor_map[:begin_norm_axis] + ("None",) * len(gamma_tensor_map)
NormDistributedOp 返回的 out_layout 将归一化维度设为 "None"(不分片),这是 mean/rstd 辅助输出的 layout,而非主输出的 layout。主输出应保持与输入相同的 layout。
三、性能级别 (Performance) — 严重影响训练效率
10. 所有通信操作均为同步执行 — 无计算与通信重叠
这是 最大的性能问题。
unshard 阶段(forward/backward 前的 all-gather):
for param in self.sharded_hsdp_params:
param.unshard()
param.wait_for_unshard()
每个参数的 all-gather 逐个同步执行,完成后才开始下一个。
post_backward 阶段(reduce-scatter + all-reduce):
reduced_grad, _ = hsdp_param.reduce_scatter_grad(
dtype=self._reduce_dtype,
reduce_op=self.reduce_op_type
)
...
reduced_grad, _ = hsdp_param.all_reduce_grad(
grad=reduced_grad,
reduce_op=self.reduce_op_type,
)
两者均为同步操作(async_op 默认 False),没有计算与通信重叠的可能。PyTorch 原生 FSDP2 通过独立的 CUDA stream 实现 overlap,此处完全缺失。
11. V2 路径无梯度 Bucketing 支持
self.comm_async = False
self.comm_fusion = False
self.bucket_size = 9999
self.grad_fusion = False
配置硬编码关闭了通信融合和梯度融合。每个参数独立发起一次 reduce-scatter/all-reduce 小集合通信,大量小消息无法利用网络带宽。
12. Layout 构造存在双重 deepcopy
def __call__(self, *alias_tensor_map):
obj = copy.deepcopy(self) # 第一次
...
def _process_placement_layout(self, obj, placements):
obj.set_placements(placements)
return copy.deepcopy(obj) # 第二次
每次从 placement 构造 Layout 都执行两次 deepcopy,包括其中的 DeviceMesh(numpy 数组、缓存数据结构等)。这个路径是 DTensor 创建和重分布的热路径。
13. reduce-scatter 每次反向传播都重新分配输出 buffer
output = torch.empty(output_numel, dtype=reduce_dtype, device=grad.device)
不像 all-gather 有持久化 buffer 的机制,reduce-scatter 每次调用都 torch.empty,增加显存分配压力。
14. SDPA 序列并行构造显式 causal mask 退化了 FlashAttention 的优化
def _build_causal_mask_for_chunk(self, local_q_len, global_kv_len, split_id, device):
...
return kv_positions <= q_positions
当启用序列并行 + causal masking 时,代码物化了一个 [local_q_len, global_kv_len] 的 bool mask。这使得 FlashAttention 内核无法使用其高效的内建 causal mask 优化,长序列下会产生巨大的额外显存开销。
四、可扩展性问题 (Scalability)
15. DeviceMesh 中 rank 查找为 O(n) 线性搜索
idx = rank_list.index(rank)
出现在多个高频调用方法中(get_rank_list_along_axis, get_devices_for_axis, get_local_rank)。在大规模集群(数百/数千设备)下会成为瓶颈。应使用预构建的 rank -> index 字典。
16. Layout 缓存无上限 — 动态 shape 场景内存泄漏
class LayoutCacheManager:
def __init__(self):
self.layout_cache: Dict[str, Dict[LayoutCacheKey, Any]] = {}
Layout 缓存无淘汰策略,无容量上限。在变长序列或动态 shape 场景中(如 NLP 训练),缓存会无限增长。
17. 无词表并行 Embedding 支持
EmbeddingDistributedOp 仅处理 embedding 维度(weight 最后一维)的分片。如果用户在词表维度(weight 第 0 维)做分片,会静默产生错误结果。大模型训练中词表并行是基本需求。
18. Prefetch 需手动配置 — 无自动执行序分析
set_modules_to_forward_prefetch / set_modules_to_backward_prefetch
预取需要用户手动指定模块依赖。PyTorch FSDP2 能自动从执行图推导预取顺序。当前实现对用户要求过高,且无法做到参数粒度的细粒度预取。
五、其他代码质量问题
| 问题 | 位置 | 说明 |
|---|---|---|
Shard(0) 和 Replicate() 哈希冲突 |
placement_types.py:76,97 |
两者 __hash__ 都返回 0,降低 dict/set 性能 |
unshard() 中 raise 之后有死代码 |
hsdp_state.py:129-131 |
comm_fusion 分支因 raise 永远不执行 |
ignored_params 参数从未被使用 |
api.py:356 + 整个 init 链 |
用户传入的忽略参数列表完全无效 |
hsdp_params_with_grad / unsharded_grads 声明未使用 |
state.py:199-200 |
死代码,疑似重构遗留 |
Partial.__str__ 丢失 reduce_op 信息 |
placement_types.py:134 |
Partial("sum") 和 Partial("max") 都显示为 "P" |
avg reduction 的溢出风险 |
tensor_redistribution.py:239-242 |
fp16/bf16 下先 AllReduce-sum 再除 N,sum 步骤可能溢出 |
六、总结与建议
优先级 P0 — 必须立即修复
is_partial加括号 — 影响所有重分布路径的正确性self.platform→platform— 否则 ReduceScatter 完全不可用- DeviceMesh 缓存 key 使用完整 rank_list — 否则多 mesh 场景必出错
post_backward中reduced_grad的 NameError — HSDP 配置下必崩set_requires_all_reduce属性名修正 — 否则 API 静默无效
优先级 P1 — 影响精度和效率
- 引入异步通信 + 独立 communication stream,实现 compute-communication overlap
- 实现梯度 bucketing / fusion
- 修复 RNG offset 计算
- 修复
need_synchronize作用域 - Layout 构造消除多余 deepcopy
优先级 P2 — 提升可扩展性
- 支持不均匀分片
- 支持词表并行 Embedding
- Layout 缓存加 LRU 淘汰
- DeviceMesh rank 查找用哈希表
- 自动 prefetch 机制
整体来看,框架的架构设计(DTensor + Layout + Placement 抽象、HSDP scheduler/state/param 分层)是合理的,与 PyTorch FSDP2 的设计理念一致。但在 实现细节 上存在多个致命 bug(特别是 P0 的 5 个问题),以及在 通信性能 上与成熟框架有显著差距(同步通信、无 bucketing、无 overlap)。建议按优先级逐步修复,P0 问题应阻断合入。
schema_version: 1
source: gitcode
gitcode_repo: mindspore/hyper-parallel
gitcode_issue: 22
source_url: https://gitcode.com/mindspore/hyper-parallel/issues/22
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
This report spans tensor_redistribution.py, device_mesh.py, layout.py, fully_shard/state.py, hsdp_scheduler.py, random.py, and several shard and parameter modules. Start by triaging and reproducing the five P0 findings, then inspect nearby tests or add focused regression coverage for each confirmed problem. Done means the selected issues are fixed without introducing incorrect layouts, communication failures, or gradient errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, distributed-systems, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100