news 2026/9/30 2:33:13

Genesis 开发规范全景解析:内核编写、数据访问与测试纪律的工程实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Genesis 开发规范全景解析:内核编写、数据访问与测试纪律的工程实践指南
  • 物理引擎
  • 具身智能
  • 机器人
  • 人工智能

【免费下载链接】genesis-world

Simulation platform for general-purpose robotics & embodied AI learning.

项目地址:https://gitcode.com/GitHub_Trending/genesi/genesis-world
点击查看免费下载

导读:本文以仓库根目录的 CLAUDE.md(Genesis Development Guidelines)为绝对主体,系统讲解 Genesis 仿真平台面向协作者与 AI 编码助手的全套开发准则,覆盖基础编码纪律、Quadrants 内核编写、批量数据访问、公开 API 设计、代码风格与测试方法论。文章在完整继承规范全部要点的基础上,结合genesis/源码实现、tests/测试套件与pyproject.toml配置逐一佐证,帮助读者理解每条规范的底层动机,并能在实际开发、测试与代码评审中直接落地。

规范文档的定位与整体结构

CLAUDE.md是 Genesis 项目面向 AI 编码助手(如 Claude)和贡献者的权威开发准则,其姊妹文档 CODING_GUIDELINES.md 是面向人类贡献者的补充约定,二者共同维护仓库的代码质量基线。全文按主题分为 11 个板块:

板块核心关注点
Miscellaneous基础编码纪律:类型、命名、异常、遗留代码清理
Priorities冲突时的取舍优先级
Data AccessQuadrants 与 torch/numpy 之间的数据转换与零拷贝
Kernels@qd.kernel/qd.func的可微分写法与参数约定
API Design公开 API 的自洽性与易用性约束
Style文档字符串、注释、命名与排版
Testing Guidelines测试组织、物理断言、步数与容差预算
Git Worktree 测试可编辑安装模式下"测错引擎"的陷阱
集群测试Slurm 包装器gs-srun下的测试实践
Apple 软件渲染器在本地复现 macOS CI 渲染失败
Tooling & Contributingruff、pre-commit、PR 标题规范

从源码结构看,这些规范并非孤立的写作要求,而是直接对应仓库的真实架构:Quadrants 作为底层计算内核,提供可自动微分(autodiff)的张量/字段抽象与@qd.kernel装饰器(见 array_class.py);Genesis 在其上封装了面向用户的求解器、实体与场景 API。规范的核心矛盾因此始终是:内核层追求极致性能与可微分性,用户层追求清晰、稳定、可预测的公开 API。

基础编码纪律(Miscellaneous)

强类型数据:用 dataclass / NamedTuple 取代 dict

规范明令禁止用普通 dict 打包属性,要求使用强类型命名数据结构(dataclasses 或简单 NamedTuple)。唯一的例外是刚体资产解析器产生的中间信息:因为"格式声称的内容在解析完成前是不完整、无结构的",所以允许以普通 dict 形式在解析器与消费它的解析逻辑之间传递,但任何内置对象不得持有它。

这一约束在源码中有清晰体现:genesis/engine/solvers/rigid/rigid_solver.py中所有求解器状态字段都是声明式的 Quadrants 字段定义,例如qpos0=V(dtype=gs.qd_float, shape=(solver.n_qs_, _B))、n_awake_dofs=V(dtype=gs.qd_int, shape=(_B,))(见 array_class.py)。Quadrants 数据类的 dtype 定义集中在 array_class.py,而 genesis/init.py 统一导出了qd_float、qd_int等类型。

禁止 getattr / hasattr,用 None 初始化与 isinstance 判断

动态属性探测会破坏类型可推导性,规范要求一律改用None初始化配合isinstance(...)判断,且判断应直接写在if语句中,不引入临时变量。

领域对象命名的正确性语义

领域对象只有三个名词:entity、link、geom,禁止自创 "body"、"object"、"piece" 等新词。这不仅是风格问题,更是正确性提示:刚体单位是 link,因此必须按entity.links→link.geoms的层级迭代几何体,而不是按 entity 聚合。规范同时要求把has_any_rigid_coupling这类"属性型"实例方法转换为 property(见 rigid_solver.py 的使用方式)。

类型转换的"零容忍"原则

float()/int()/.astype()/np.asarray()/dtype=一律禁止,除非严格必要——即外部接口强制要求 dtype(如 igl 的 float64/int64、内核参数的原生 Python 类型),且不得重复转换已被后续步骤转换过的值。numpy 标量天然支持索引、比较与算术运算。torch 张量转 numpy 必须走 misc.py 的tensor_to_array(内部先tensor_to_cpu处理 GPU→CPU 迁移),禁止手写detach().cpu().numpy();需要指定 dtype 时通过tensor_to_array(x, dtype=...)参数传入,而不是链式.astype。

异常与 assert 的分界

"能由用户输入触发"的情况抛异常,"代码内部流程不变量、无公开 API 暴露"的情况用assert。破坏物理正确性的关键错误必须抛异常(必要时 re-raise 澄清错误信息),绝不允许用 warning 掩盖。NaN 必须通过现有的 errno 机制中止仿真(可定义新错误码),同样不接受 warning。

清理与去重

规范要求删除全部遗留代码(不保留 deprecation / legacy 兼容层)、删除未使用的辅助函数(如has_coupling_type)、不允许代码重复、禁止注释掉的 print(一律改为 logging debug trace)、禁止"按最大尺寸预分配"(必须精确分配所需内存)。

优先级:质量优先于一致性

当从 PR、外部项目或旧实现移植代码时,代码质量优先于源码一致性:Genesis 的约定永远优先于原代码风格,原逻辑只作为参考而非实现风格。在构建/初始化路径上,可维护性优先(只要性能合理即可),坚持使用公开 API;在运行时热路径上,效率优先,最小化 GPU-CPU 传输、使用批量操作。

数据访问规范(Data Access)

这是规范中技术含量最高、与底层运行机制绑定最紧的部分,核心对象是 Quadrants 字段。

统一转换入口:qd_to_numpy / qd_to_torch

禁止直接调用 Quadrants 数据实例的.to_numpy()/.to_torch()方法,必须使用genesis.utils.misc提供的qd_to_numpy/qd_to_torch。源码实现(见 misc.py)显示:

  • transpose=True时应始终传入,把批维移到最前([B, n, dim]),与公开 getter 约定对齐;
  • copy=False仅在 CPU 后端且启用零拷贝时受支持,GPU 数据转 numpy 必然需要拷贝;
  • 转换应提升到循环之前,一次调用后索引本地数组,禁止在紧循环内反复调用qd_to_numpy;需要子集时把切片作为qd_to_numpy的输入参数,而不是先转换再切片;
  • 不要给qd_to_torch/qd_to_numpy传copy=参数(当返回值是全新算术结果、无内部别名外泄时),让方法自行决定零拷贝或拷贝;只有"裸字段视图可能逃逸并被修改"时才用copy=True。

零拷贝写入路径的规则

  • 需要被写入的qd_to_torch视图必须先绑定到具名局部变量(如flags_t = qd_to_torch(..., copy=False)后再flags_t[mask] = ...),禁止通过匿名链修改;只读场景则相反,可以放心内联链式操作(如qd_to_torch(f).T.reshape(...).gather(...))。
  • 零拷贝访问器应原样透传选择(selection);写入属于拷贝语义的 setter(每环境一个向量/标志/一行)在gs.use_zerocopy成立时走视图路径。视图路径上禁止使用Scene._sanitize_envs_idx(见 rigid_solver.py 等处的传统用法),而应把选择原样交给indices_to_mask(定义于 misc.py),再以broadcast_tensor(misc.py)对view[mask].shape广播值、以assign_indexed_tensor(misc.py)执行写入。indices_to_mask从末尾计数的负边界是其已知缺口,应在该函数内修复,而不是逐个访问器绕开。
  • 零拷贝路径只依据gs.use_zerocopy分支(见 genesis/init.py,由环境变量GS_ENABLE_ZEROCOPY控制),禁止 try/except 探测。

构建期与运行期的数据获取分工

  • 构建/初始化期:通过 entity/link/geom 公开 API 迭代(entity.links、link.geoms、geom.init_verts、link.get_pos()等),只有效率、简洁性或可维护性确实需要时才下沉到低层求解器字段。
  • 运行期:批量转换用qd_to_numpy/qd_to_torch,禁止逐字段索引field[i];优先用 torch 零拷贝更新其他求解器的内部状态(numpy 零拷贝仅 CPU 后端支持)。
  • 构建期应把 Python 引用(entity、link 对象)存入 dict 供运行期查找,而不是每步从求解器字段重新推导。
  • 派生多个原始求解器字段的量应归属为单个向量化求解器 getter,而不是由消费者自行组装或写专属逐元素@qd.kernel再重索引——字段访问耦合与 batched/non-batched 处理应集中在单一经测试的位置。批量参数用envs_idx=env_idx if batched else None处理,而不是针对批处理分支;getter/setter 应对所有环境一次性处理(IPC 相关的可退化为 for 循环),禁止按 env idx 反复调用。

Metal 流同步

在一批 torch 零拷贝写入之后、下一个 Quadrants 内核读取缓冲区之前,调用者需调用一次torch.mps.synchronize()。执行此类写入的辅助函数(如qd_zero_grad)内部从不自行同步,这一责任在调用方。源码 misc.py 中确实可见 Metal 后端的同步处理注释与torch.mps.synchronize()调用。

内核参数的数值类型

禁止向内核参数传递 numpy 标量(如numpy.float64)——它会破坏 Quadrants fastcache 的弱引用机制。必须强转为原生 Python 类型(float()、int())。

Kernel 编写规范

可微分的结构性要求

每个内核都必须能被 Quadrants 的 autodiff 反向执行,因此结构性禁令非常严格:

  • 顶层只能由for循环组成,禁止continue、禁止while;
  • "门控"用静态条件下置位、再由if测试的标志实现,不能用提前continue;
  • 遍历块时用for覆盖 dof 范围并做块起始测试;条带步进用for i_chunk_ in range((n + BLOCK_DIM - 1) // BLOCK_DIM),再以i = i_chunk_ * BLOCK_DIM + tid配合if i < n门控;
  • 手工编写反向内核被强烈反对,除非有非常充分的理由(如反转 autodiff 无法展开的迭代算法——约束求解、逆运动学)。

间接索引的绑定约束

从字段读出的索引必须先绑定到临时变量再用于索引其他字段(如i_r = rigid_info.links_root_rank[i_l]然后rigid_info.roots_link_end[i_r])。a[b[i]]这类嵌套间接索引在内核与 func 中禁止。

规范参数顺序(Canonical Parameter Order)

所有 kernel 与 func 遵循固定参数顺序:先是各类索引(循环索引、envs_idx等索引张量、joint_idx等索引标量或范围起止),然后是动态原生 Python 标量与定长 Quadrants 向量(每调用值:位置、四元数、穿透量),接着是 kernel/func 专属张量,然后依次是全部 state 结构体、info 结构体、静态配置,再是"实践中实为常量"的参数(形状整数、eps、容差),最后是编译标志——先是静态的(qd.template()布尔量),再是运行时原生布尔量(如write_L: bool,保持动态以省去每种取值的一次编译)。errno单独跟在最后一位。state/info/config 各组内部的组件顺序固定(dyn、rigid、collider 含 mpr、gjk、support_field、sdf,然后是 constraint)。调用点按签名顺序传参。

静态配置与运行时信息的边界

静态配置只能容纳有界选择:布尔、枚举、小固定集合内的整数。而"可取任意值的整数"(环境数量、叶子/面数量、由此导出的位宽)属于运行时信息——应为内核中的张量形状、info 张量或普通整数参数。静态整数会按值编译内核,场景间一变值就破坏预编译(刚性求解器从不对环境数量做键控即是参照)。

类型标注与命名

  • 每个 kernel 和 func 参数都必须有类型:标量用原生 Python 类型(int/float/bool),定长向量/矩阵用qd.types.vector(n)/qd.types.matrix(n, m);类型多态辅助函数的参数可保持不标注。
  • 定长向量注解中不得写 dtype:写qd.types.vector(3),绝不写qd.types.vector(3, dtype=gs.qd_float)。
  • 新代码一律用自由函数@qd.kernel,不用@qd.data_oriented;类型多态参数使用 array_class.py 中的V_ANNOTATION。唯一例外是 FEM 求解器,它延续旧的@qd.data_oriented方法模式,新增内核必须与之保持一致。
  • 内核/func 调用中,同名参数按位传递(self-named),匿名常量(裸字面量、尾部静态标志)按关键字传递;结构体成员与父结构体同传的 func 调用只能关键字传参(因为 quadrants 的位置参数展开会复制成员)。

qd.func 内联与编译预算

每个内核只内联一次它所调用的每个qd.func实例:同一辅助函数被两个不同参数的分支调用会编译两次,模板参数取两个值也会让被调方编译两次,qd.static(range(R))下重函数体会编译 R 次。规范给出的对策:在运行时分支挑选参数只调用一次、以运行时计数循环项(重算廉价逐项值而非携带逐项寄存器)、需要提前退出时用单次迭代for加break(collider 的模式)、可微内核中用if门包住顶层循环体(break/continue被拒)。CI 基准测得的编译时间是裁决标准:低于 2% 无所谓,8% 及以上必须处理,中间地带酌情判断。此外,纯读写数据访问器在热路径上应优先走零拷贝视图(qd_to_torch(..., copy=False)/qd_to_numpy(..., copy=False)),内核仅作回退——只为搬运/改写字段写微型内核,用微秒级工作付出完整内核分发开销不值得。

API 设计

单一正确值的自动解析

如果某选项在给定上下文中只有一个有效值(如 IPCCoupler 下必须enable_collision=False),不要强迫用户设置:默认None,初始化时解析为正确值,用户显式给出冲突值时抛异常。解析逻辑可基于其他选项、场景内容甚至运行时状态,但必须充分文档化。

辅助函数的克制

不为一两行直白逻辑(更别说单表达式)包装辅助函数,无论有多少调用者:直接内联,需要名字时作为局部变量。单一用户场景的专属辅助函数(single-use helper)一律禁止,除非它完全独立且通用(数学函数、转换工具);避免私有实例方法。单值返回值不得包进具名结构体(一字段 NamedTuple/dataclass 是死样板),只有真正多字段返回才用具名结构。

数组输入的类型别名

索引/数组输入选项必须使用项目的 array-like 类型别名(IArrayType/OptionalIArrayType/FArrayType/Vec3FType等,定义于 typing.py),禁止裸tuple[int, ...]/list[...]。原因在源码中可见:Options是严格 Pydantic(ConfigDict(strict=True)),裸类型集合字段会拒绝用户自然传入的 list 与 numpy 数组(如dofs_idx_local=[0, 1]),而别名携带strict=False,可将 array-like 输入强制转换为 tuple。模仿同级选项字段的类型写法,如filter_link_idx: OptionalIArrayType。

风格规范(Style)

文档字符串

  • 以一句不超过 200 字符、不点名参数的短句开头,更多内容空行后分段展开;参数在各段中说明。
  • 描述现状而非历史:"该函数现在做什么",不写"曾做什么"或"为 X 添加了 Y";若行为是某 bug 修复所需,以当前不变量解释(如"必须处理 Z 情形以避免 Y")。
  • 面向用户的选项文档必须陈述权衡(cost AND benefit,何时选何值),只讲"做什么"而不讲代价的文档是无用的;用 Genesis 术语说明,不引用外部引擎(如 MuJoCo)与内部实现机制(约束"行"、耦合、锥投影、内核、分解路径),那些属于开发代码注释。
  • 函数级描述放 docstring,禁止在函数体顶部放成块的#注释;getter/方法开头写一大段#散文是错的——那是它的 docstring。文档字符串不得声明代码未提供的契约(未保证的数组形状、内存布局)。

命名

  • 布尔变量/字段/属性以is_/has_前缀开头,现在时优先(is_fixed、is_convex、has_multi_island_structure);was_只用于真正的过去时标志(宁取is_cached_loaded而非was_cached)。裸名(hibernated)或did_(did_fuse)均非法。前缀问的是状态而非输入参数:函数/方法/内核的布尔参数命名"调用者请求什么"(refresh_position、scale_inertia、in_place),不加前缀。
  • 变量名以名词开头命名其持有对象种类(rejected_kinds、links_offset_pos),不写裸形容词/分词(rejected、left_out);例外是布尔(上述前缀)与索引/循环变量(i_<type>约定)。
  • 容器避免泛化的all_前后缀,宁可具体(joints_xanchor、links_inertia_i、geoms_pos、entities_quat、verts_idx);避免纯变量名赋值(mass_mat_env = mass_mat_all);可变容器优先"清空复用"(self.abd_data_by_link.clear())而非重新分配(self.abd_data_by_link = {})。
  • 使用flatten()、reshape((-1,))、ravel()代替 squashing 是不允许的,且优先[..., 0, :]而非squash(axis=-2)。
  • ASCII only:源码(代码、注释、docstring)不得出现非 ASCII 字符——写tau而非希腊字母、qddot而非带双点的 q、*而非中圆点、-/--而非 em dash、纯# ---而非制表符分隔线。缩写在每次 docstring/注释首次出现时全称拼出并括号标注缩写(如 "positive semi-definite (PSD)")。

注释

  • 注释解释为什么:它保护的不变量、诱人替代方案的失败模式;保持通用、现在时,不锚定易腐细节(测量值、基准数字、特定单测/示例)。
  • 每条事实只存在于一个注释中,其他站点交叉引用它并保留一行指向真源(如 "see nt_H in array_class.py")。
  • 注释独立成行置于所注解代码上方,尽量不尾随行内注释;不写无用/循环注释(不重述名字、类型、控制流已传达的信息,不写同义反复)。
  • 段落结尾不得以单字词单独成行;行填充至 120 字符宽。
  • 已知上游 bug 记# FIXME:并命名 issue,最多两行,含 tracker 引用(如# FIXME: quadrants#887 - ...)与 workaround 的代价。

其他硬性风格

  • 写朴素陈述句(主语-动词-宾语),用标准仿真词汇(质量矩阵、Jacobian、逆权重、运动学树、子步),不自造动词。
  • 不用分号连接散文,不用破折号当括号:写两个句子或用圆括号。
  • 局部变量以其是什么命名,而非其被测属性(场景变量叫scene_from_substeps,不叫shorthand)。
  • 禁止用浮点==/!=比较,即使主机端值也如此:用np.allclose(a, b, atol=gs.EPS)或显式界。
  • 不写第三方对象的私有状态;自持记录在旁并经 property 暴露。
  • 叙述"是什么"而非"不是什么":去掉 "not X"、"unlike Y",除非读者天然会做错误解读。
  • 导入分组:标准库、通用第三方(numpy/torch/tqdm…)、其他第三方、自家库(quadrants…)、genesis 自身(先绝对后相对),组间空行、组内字母序;改动一个导入就必须重整整个模块的导入。禁止导入别名(Cloth而非ClothMaterial)。
  • 匿名字面量按关键字传递以在调用点显其含义(_adaptive_params(verts, faces, aggressiveness=7)),模块局部 qd.func 调用中的数值常量可保持位置传参。
  • 换行按嵌套层级全有或全无:能一行放下(≤120 字符)必须单行;溢出则整体收进单个续行;再溢出则每个参数一行;展开的外层调用中,能单行放下的嵌套调用保持单行。嵌套数据字面量(矩阵、坐标/边表)是例外:即便整体能放下也保持一行一行。
  • 一个if子句要么全是静态条件要么全是动态条件;共享内存数组(qd.simt.block.SharedArray)用sh_前缀。
  • 禁止用快照相等比较检测状态变化:Genesis 有专用变更检测设施(solver 的 StateChange 订阅机制,见 base_solver.py 中的StateChange枚举与mutates装饰器),缓存运行数据再轮询对比新副本的做法被禁止;设施缺失或粒度不足不是借口——扩展机制,绝不退回到快照比较(这是整个 PR 被拒的充分理由)。
  • 非 solver 类(materials、couplers、entities)不得使用@qd.data_oriented;目录内命名保持一致(如 IPC 示例统一ipc_*.py前缀)。

测试规范(Testing Guidelines)

组织与命名

  • 测试按组件再按能力组织:tests/下每组件一个目录(rigid/、deformable/、particles/、ipc/、coupling/、sensors/、rendering/、parsers/、core/、integration/、benchmarks/),每能力一个文件(如 test_collision.py、test_imu.py)。新测试进既有能力文件,只有真正的新能力才开新文件。
  • 优先加强既有测试而非新写:扩展场景与断言,或用@pytest.mark.parametrize加维度。测试以所验证的特性命名,而非被仿真场景:test_reject_offaxis_contact_on_authored_decomp而非test_stacking_tower_stability;名字不以模块/文件夹名作前缀(tests/sensors/test_temperature.py::test_grid_sensor_contact_and_reset,绝不写test_temperature_grid_...)。
  • 单元测试不得有 docstring——好的测试名胜过短 docstring;代码注释仅在强动机下允许(解释测试体无法传达的内容)。绝不写钉死已知缺陷、验证 deprecation warning、或测试"不提供能力"的测试;但"拒绝无法处理的输入"是行为,应被断言(若继续会损坏用户所得)。

打包测试(Pack Tests):一次场景构建

一次测试只建一个场景,宁可牺牲可读性也必须避免额外构建(构建慢且计算重)。偏好顺序:一个综合场景(多样实体与选项)> 同场景内不同配置实体 > 必须互不干扰的布置在同一场景内远距摆放 > 通过n_envs扫配置 > 参照仿真并排折叠进同一场景。"注定被拒绝的场景"构建零成本、不占预算,一个测试里多个没问题。

断言物理,而非执行

"仿真无错跑完"不是测试。检查有物理意义的量:自由落体位移(z = z0 - 0.5*g*t^2)、无地面穿透(min_z > -d_hat)、静止时速度→0、接触阻止下落。无解析期望时可先跑一次取参考值硬编码,用宽松容差断言,并加FIXME注释请求日后替换为物理感知断言。用 assertions.py 的assert_allclose/assert_equal:精确比较优先assert_equal(等价于零 atol/rtol),tol=同时设置 atol 与 rtol 且适用于量级为 1 的量,量级固定远离 1 的(如0..255像素通道)用atol=。提交针对特定缺陷的测试前,先故意破坏实现并观察断言失败。

步数与容差预算

步数预算是硬性约束,按测得而非猜测的最少步数来定:浮点敏感量加 10% 或进位到 50(取更大者)。层级:<100步无碍、100-300灰色(需说明)、>300近乎禁止(全测试套件仅允许 4-5 个)。同时约束总 env 步数(steps * n_envs):500 步 × 16 env 的稳定期(8000)不可接受。批量测试用n_envs=[0, 2]参数化(多 env 是形状 bug 的藏身处),且不添加 conftest fixture 已控制的死参数维度(如backend)。

  • 验证数学而非规模:一个约束足以证明理论(对 1 成立即对 1k 成立),正确性不需要大/高 DOF 场景;真实多体场景仅在验证单约束无法验证的东西(端到端稳定性)时才值得花步数。
  • 优先浮点稳健检查:单次约束求解比较(从固定状态一步)稳健;跨数值不同代码路径的多步轨迹比较不稳健(fp32 误差累积、正确路径也会发散)。跨引擎(MuJoCo)一致性是现实检查,解析闭式是数学检查。回归测试是被动添加(bug 浮现时),不做推测性添加。
  • 梯度-有限差分(FD)容差钉在测得地板:地板T = max|ana - fd| / (1 + |fd|)(配置 eps 下,取 CPU 与两个 GPU 架构最坏值),容差取地板的 1.5x-5x,值只能取自 {1, 2, 5}e-X。不追的地板是 fp64 的 1e-10 与 fp32 的 5e-5;eps 按精度设置(fp32 大、fp64 小)。地板恰为 0 说明检查空洞——修 loss 而非容差;与 eps 无关的残差是解析 bug,绝非 FD 伪影。
  • 向物理精确收紧:宽松容差掩盖真实行为差异;收紧导致测试失败时深挖根因而非放宽;用精确断言路径(全 horizon、所有阶段)验证修复,不用廉价代理。
  • 精确解析动力学检查:强制gs.integrator.Euler使有限差分qacc等于求解器结果,并同时计入刚体转动惯量与隐式阻尼一阶修正(effective_inertia = I + damping*dt,见test_position_control)。

场景构建细则

  • 每个scene.add_entity/scene.add_sensor/gs.morphs.*/gs.options.*调用每行一个选项(ruff 不强制,需手写);关键字参数遵循被调函数声明顺序(add_entity(morph=..., material=..., surface=...)),关键字不授权重排。
  • 只设严格必需的选项,默认值不显式设置(除非计算读取该值:gravity、dt必须显式设置以匹配断言)。
  • 标量/list 直接传递(control_dofs_force(TAU)、set_dofs_kp([...])、inverse_kinematics(pos=[...], quat=[...])),不包np.full/np.array/单元素[...];一次性目标向量内联而非命名。
  • 自定义 MJCF/URDF 模型用xml.etree.ElementTree在 fixture 中构建,返回ET.tostring(mjcf, encoding="unicode")直接传给 morph 的file=(FileMorph.file接受内联 XML 字符串,见 conftest.py),绝不写临时 XML 文件、绝不write_text/f-string 拼 XML。
  • 需落盘的临时资产用 session 级asset_tmp_pathfixture,tmp_path仅 per-test;无模块级测试常量/辅助/参数化场景列表,参数从建好的模型读回(get_dofs_armature、get_dofs_damping,见 rigid_solver.py)。
  • 重复样板藏进 fixture,但无回报的间接层严格有害(默认有害,须以真实收益证明);场景搭建留在测试体(单测读起来像示例脚本),宁重复也保持显式自洽,不做过早分解。
  • 每步断言不做float(...)/.item()强转:设计场景使断言无条件成立(如预热循环把关节转起来),再对整张张量.all()断言。
  • 每个调用scene.step()的测试取show_viewerfixture 并设置带相机视角的ViewerOptions,便于启用 viewer 调试。
  • 需要接触的场景必须有可动物体:双固定 geom 对在构建期被丢弃,把固定实体沉入表面不会产生接触,还会永久埋没其几何体渲染。
  • FEM 实体位置:entity.get_state().pos形状[B, n_verts, 3],用[..., 2]跨 env/顶点选 z;刚体实体:entity.get_pos()返回[B, 3]或[3],用np.atleast_1d(...)[..., 2]与.all()做多 env 检查。

测试输出的纪律

绝不内联过滤/截断测试输出(pytest ... | tail/| grep):过滤器夹在 pytest 与磁盘之间会毁掉失败名的唯一副本并掩盖退出码。重定向完整输出到日志文件再从文件提取。绝不删除或弱化既有断言/测量来压掉失败(本地或 CI)——连同数据上报并询问;只在单机成立的阈值是校准问题。Bug 修复 PR 必须带回归测试:在main上失败、带修复后通过,加入已覆盖被破坏能力的那个测试。

从 Git Worktree 运行测试:可编辑安装的"测错引擎"陷阱

包以可编辑模式指向主检出安装,因此 worktree 的genesis/默认不被导入——worktree 的tests/实际跑的是主检出的genesis/,引擎改动看似无效、所有结果作废。规范给出的识别症状:引擎改动无效果、worktree 中新增的内核print()不输出、引擎变体 A/B 每臂位级相同——先按此陷阱排查,再谈物理解释。

正确做法:

  • 从 worktree 调 pytest 时始终传PYTHONPATH=$PWD:cd <worktree> && PYTHONPATH=$PWD pytest -n 8 tests/rigid;集群同理PYTHONPATH=$WORKTREE pytest ...。
  • 每会话验证一次而非假设:PYTHONPATH=$PWD pytest -n 0 -s -k <any test>并print(genesis.__file__),确认打印路径以 worktree 开头。
  • 裸python script.py从 worktree 运行确实会选中 worktree(cwd 先于可编辑路径),因此同一目录下脚本与 pytest 可能跑不同引擎,二者结果不可比较。
  • 并行运行时给 worktree 独立编译缓存:QD_OFFLINE_CACHE_FILE_PATH=<scratch>/quadrants GS_CACHE_FILE_PATH=<scratch>/genesis。worktree 引擎不同于填充共享~/.cache的引擎,每个内核都是新的,xdist worker 竞争写同一缓存会损坏条目:表现为上百个明明在作用域内的QuadrantsNameError、散布在单跑通过的测试中、第二次运行因坏条目持久而更糟。重定向缓存而非删除共享缓存。

集群测试实践

基于真实集群环境(ssh genesis-coreweave,代码路径/mnt/home/duburcqa/workspace/src/genesis):

  • Git 操作(fetch/checkout/pull)必须在登录节点完成,不能在gs-srun内(计算节点容器无法访问 GitHub)。
  • 始终用gs-srun(Slurm 包装器)分配 GPU 节点,绝不在登录节点直接跑 pytest/python;绝不手动 source venv——容器镜像已带正确环境,bash -lc登录 shell 自动配好。
  • 组合模式:ssh genesis-coreweave 'bash -lc "cd /mnt/home/duburcqa/workspace/src/genesis && git pull && gs-srun --partition=rtx-high --nodes=1 --gpus=1 bash -ilc \"cd /mnt/home/duburcqa/workspace/src/genesis && pytest -n 10 tests/ipc -v --no-header 2>&1\""'。
  • 示例测试需-m examples覆盖 pyproject.toml 的默认标记过滤(默认-m not (benchmarks or examples))。
  • 本地文件复制用scp <local_path> genesis-coreweave:<remote_path>后gs-srun运行。
  • 计算容器挂载/mnt/home,不挂登录节点的/tmp:登录节点/tmp下的脚本/补丁/输出目录在gs-srun下不可见,一切暂存到/mnt/home/duburcqa。
  • 交互登录 shell(bash -ilc)设置noclobber:cmd > file在文件已存在时报 "cannot overwrite existing file" 且静默无输出;固定路径再生成日志时先rm -f file或用>| file。
  • 用bash -ilc "source script.sh"跑暂存脚本,绝不bash -l script.sh:容器 Python 环境只在交互登录 shell 初始化,非交互bash -l在gs-srun下无 venv(ModuleNotFoundError: genesis)。脚本暂存于/mnt/home后 source 之,同时避开超长内联命令的嵌套引号问题。

复现 Apple 软件渲染器失败

GitHub Apple Silicon macOS runner 都是虚拟机,虚拟化 GPU 无 OpenGL,渲染回退到 Apple Software Renderer(该渲染器半损坏)。macOS 专属渲染失败源于测试触及其故障模式,因此单元测试要谨慎依赖渲染特性。在本地任意 Mac 强制复现:在任何 GL 上下文创建前劫持 pyglet 无条件追加的两个像素格式属性:

# force_sw.py - import before any GL context is created, e.g. 'pytest -p force_sw' with PYTHONPATH set from pyglet.libs.darwin import cocoapy cocoapy.NSOpenGLPFAAllRenderers = 70 # NSOpenGLPFARendererID cocoapy.NSOpenGLPFAMaximumPolicy = 0x00020400 # kCGLRendererGenericFloatID
  • 这只影响 pyglet 创建的上下文,离屏渲染须以PYOPENGL_PLATFORM=pyglet路由回 pyglet 平台(其 macOS 默认 CGL,pyglet 对此一无所知)。
  • 用与 macOS CI 相同的标志运行:PYTHONPATH=<dir-with-force_sw.py> PYOPENGL_PLATFORM=pyglet GS_TORCH_FORCE_CPU_DEVICE=1 pytest -p force_sw --dev --logical --backend cpu --forked <tests>(CI 另选-m 'required and not slow')。
  • 生效验证:Genesis 日志输出 "Software rendering context detected",且scene.visualizer.is_software为 True。
  • 已知故障模式一:相机视锥外的顶点几何被错误光栅化,破坏像素比较——地面平面默认plane_size实际无限(1km × 1km),须给有限尺寸并置于视图内。
  • 已知故障模式二:软件渲染后端因性能强制禁用阴影映射,快照场景必须显式关阴影(rasterizer 的shadow=False);硬件 GL 带阴影生成的快照永远无法匹配。

工具链与贡献流程

  • Lint/format 用 ruff(check + format,行宽 120,见 pyproject.toml)经 pre-commit 执行:pre-commit install后每次提交自动运行。
  • PR 标题带括号标签:[BUG FIX]、[FEATURE]、[MISC](默认会改变仿真物理:不同模型或不同默认参数;求解器内部重构属[MISC])、[CHANGING]、[BREAKING](API 破坏)。提交标题是纯单行句、无标签;PR 与提交标题都以句号结尾。
  • PR 标题陈述对最终用户的收益而非实现,实现细节进 PR 描述。
  • 贡献者须遵守 CODING_GUIDELINES.md 与.github/contributing/下的参考文档(ARCHITECTURE、TESTING、CODING_CONVENTIONS、EXAMPLES、PULL_REQUESTS、USD_PARSER);冲突时先问。

结语

把CLAUDE.md的规范与genesis/源码对照阅读,可以清晰地看到一条贯穿始终的主线:所有约束都服务于"可自动微分的性能内核 + 稳定清晰的公开 API"这一对目标。内核侧通过参数顺序、静态/运行时信息切分、qd.func内联与零拷贝视图换取编译速度与 GPU-CPU 传输的最小化;用户侧通过强类型数据结构、类型别名、单一正确值自动解析与权衡式选项文档换取 API 的可预测性与自洽性;测试侧则用打包场景、物理断言、步数与容差预算保证 CI 成本可控且断言真实有效。对于希望为 Genesis 贡献代码(无论是人类开发者还是 AI 编码助手)的读者,这份指南既是一份可执行的检查清单,也是理解仓库设计哲学的入门地图。

  • 物理引擎
  • 具身智能
  • 机器人
  • 人工智能

【免费下载链接】genesis-world

Simulation platform for general-purpose robotics & embodied AI learning.

项目地址:https://gitcode.com/GitHub_Trending/genesi/genesis-world
点击查看免费下载

相关推荐

上一篇:Watermill SQLite Pub/Sub 实战:CGO-free 双驱动(ModernC / ZombieZen)的事件持久化指南
下一篇:Wasp 用户名密码认证:从零搭建自定义登录注册 UI(wasp/client/auth 实战指南)

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

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

GoogLeNet 含并行连结的网络(Inception)详解:从原理到多框架动手实现

人工智能深度学习机器学习教程 【免费下载链接】d2l-zh 《动手学深度学习》&#xff1a;面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。 项目地址&#xff1a; https://gitcode.com/GitHub_Trending/d2/d2l-zh 点击查看 免费下载 《动手学深度学…

作者头像 李华
网站建设 2026/9/30 2:32:05

软文推广采购避坑指南:从行业价值到头部服务商选型全攻略

在品牌营销精细化、流量成本持续攀升的当下&#xff0c;传统硬性广告投放存在成本高、时效短、留存弱、信任度低等短板&#xff0c;已经难以满足企业长效品牌建设需求。而软文推广凭借公信力强、内容留存久、搜索引擎收录稳定、适配AI信息采信逻辑、可沉淀品牌数字资产等核心优…

作者头像 李华
网站建设 2026/9/30 2:31:45

海外储能升压一体机:箱变保护如何适配环网柜狭小空间

摘 要&#xff1a;储能升压一体机行业处于高速扩容阶段&#xff0c;集成化、液冷大功率机型成为主流&#xff0c;一体化方案凭借成本、施工、运维优势逐步替代传统分体设备。储能升压一体机高低侧多为环网柜&#xff0c;空间小&#xff0c;且变压器容量大&#xff0c;因此需要把…

作者头像 李华
网站建设 2026/9/30 2:31:27

Linux下shell执行多用户命令

一、前言 国庆期间&#xff0c;个人服务器断电了&#xff0c;导致服务器上面数据库等应用全部关闭了&#xff0c;但是启动应用时候需要切换到对应的用户&#xff0c;让同事启动&#xff0c;又存在各种应用比较麻烦&#xff0c;个人应用也没有提前编写维护手册&#xff0c;导致服…

作者头像 李华
网站建设 2026/9/30 2:31:10

深度拆解|Dif.Sh 开源特性开关(Feature Flags)技术架构与工程落地全解析

一、前言在现代前端、后端及全栈持续交付体系中&#xff0c;特性开关&#xff08;Feature Flags/Feature Toggles&#xff09;是支撑灰度发布、渐进式迭代、A/B测试、故障快速回滚的核心工程化能力。传统特性开关解决方案普遍存在架构割裂、配置与代码分离、版本不可追溯、评审…

作者头像 李华