Skip to content

feat(phyai):Adds GR00T-N1.7 model support#41

Open
yuerqiqi wants to merge 9 commits into
MEmbodied:mainfrom
yuerqiqi:refactor-gr00t-shared-qwen3vl
Open

feat(phyai):Adds GR00T-N1.7 model support#41
yuerqiqi wants to merge 9 commits into
MEmbodied:mainfrom
yuerqiqi:refactor-gr00t-shared-qwen3vl

Conversation

@yuerqiqi

@yuerqiqi yuerqiqi commented Jul 4, 2026

Copy link
Copy Markdown

Summary

Adds GR00T-N1.7 model support to PhyAI.

This PR includes:

  • GR00T-N1.7 native model configuration, model container, runners, scheduler, and plugin entry
  • GR00T processor utilities under phyai-utils-tools
  • Qwen3-VL shared integration used by the GR00T backbone
  • Engine integration needed for GR00T runtime setup
  • Updated repository agent guidance to require pre-commit before handoff/commit

Notes

  • Replaces the GR00T-specific qwen3_vl_native.py implementation with qwen3_vl_adapter.py, which adapts the shared Qwen3-VL implementation.
  • Test scripts are not included in this PR yet.

Summary by CodeRabbit

  • New Features
    • Enabled GR00T-N1.7 native inference support, including model loading, scheduling, and end-to-end action decoding.
    • Enhanced multimodal preprocessing (deterministic video/image transforms) and improved state/action normalization and relative/action conversions.
  • Packaging
    • Added optional fastokenizer extra, and expanded core dependencies for image and vision processing.
  • Documentation / Chores
    • Updated contribution guidance to require running pre-commit checks before committing changes.

@yuerqiqi yuerqiqi requested a review from chenghuaWang as a code owner July 4, 2026 15:10
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds GR00T-N1.7 support across preprocessing, checkpoint-agnostic processing, Qwen3-VL integration, model/configuration code, CUDA-graph runners, scheduler/plugin wiring, and supporting tooling updates.

Changes

GR00T-N1.7 native inference implementation

Layer / File(s) Summary
Preprocessing utilities
phyai-utils-tools/.../models/gr00t/__init__.py, phyai-utils-tools/.../models/gr00t/ops_gr00t.py
Adds deterministic image preprocessing, Qwen3-VL image patchification, normalization, and rotation/action conversion helpers, plus GR00T package re-exports.
Processor and observation handling
phyai-utils-tools/.../models/gr00t/processor_gr00t.py
Adds typed GR00T configs and observation containers, checkpoint loading, normalization, decoding, and native or injected VLM preprocessing.
Shared Qwen3-VL extensions
phyai/.../models/qwen3_vl/modeling_qwen3_vl.py
Adds multimodal token-type handling, pre-norm hidden-state output, and prefix-aware module wiring.
GR00T-N1.7 config and adapter
phyai/.../models/gr00t_n17/configuration_gr00t_n17.py, phyai/.../models/gr00t_n17/qwen3_vl_adapter.py
Adds frozen GR00T-N1.7 config dataclasses and the GR00T-specific Qwen3-VL adapter.
Backbone and action-head modeling
phyai/.../models/gr00t_n17/modeling_gr00t_n17.py
Adds the backbone wrapper, action-head components, DiT variants, self-attention, and top-level model container.
CUDA-graph runners
phyai/.../models/gr00t_n17/model_runner_gr00t_n17.py
Adds backbone and action-head runners with graph capture/replay and denoising orchestration.
Scheduler, plugin entry, and engine wiring
phyai/.../models/gr00t_n17/scheduler_ws1_gr00t_n17.py, main_gr00t_n17.py, __init__.py, phyai/src/phyai/engine.py
Adds the scheduler, engine plugin entry, package exports, and engine plugin import registration.

Tooling and packaging updates

Layer / File(s) Summary
Pre-commit and dependencies
CLAUDE.md, phyai-utils-tools/pyproject.toml
Adds a pre-commit run convention and updates package dependencies and optional extras.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Engine
  participant GR00TN17Entry
  participant GR00TN17WS1Scheduler
  participant GR00TN17BackboneRunner
  participant GR00TN17ActionHeadRunner

  Engine->>GR00TN17Entry: setup(args)
  GR00TN17Entry->>GR00TN17WS1Scheduler: setup()
  GR00TN17WS1Scheduler->>GR00TN17BackboneRunner: setup()
  GR00TN17WS1Scheduler->>GR00TN17ActionHeadRunner: setup()
  Engine->>GR00TN17Entry: step(request)
  GR00TN17Entry->>GR00TN17WS1Scheduler: step(request)
  GR00TN17WS1Scheduler->>GR00TN17BackboneRunner: forward(inputs)
  GR00TN17BackboneRunner-->>GR00TN17WS1Scheduler: GR00TN17BackboneOutput
  GR00TN17WS1Scheduler->>GR00TN17ActionHeadRunner: forward(backbone_output, action_input, noise)
  GR00TN17ActionHeadRunner-->>GR00TN17WS1Scheduler: normalized action tensor
  GR00TN17WS1Scheduler-->>GR00TN17Entry: normalized action tensor
Loading

Possibly related PRs

  • MEmbodied/phyai#26: Extends the same Qwen3VLTextModel/Qwen3VLModel/Qwen3VLForConditionalGeneration code paths used here for GR00T integration.

Suggested reviewers: chenghuaWang

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding GR00T-N1.7 model support to phyai.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces native inference support for the GR00T-N1.7 model, adding checkpoint-agnostic preprocessing utilities in phyai-utils-tools and engine integration components (model, runner, scheduler, and configs) in phyai. Feedback on the changes highlights a bug in _process_vlm_injected where batched image inputs are incorrectly flattened, and another bug in _build_norm_params where relative action statistics fail to respect the use_percentiles flag. Additionally, performance optimization opportunities were identified, such as performing video frame stacking and reshaping entirely in NumPy to avoid PyTorch CPU overhead, and replacing a manual layer normalization implementation with PyTorch's optimized F.layer_norm.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +678 to +702
all_images: list[Image.Image] = []
for images_for_item, language in zip(transformed_images, languages):
pil_images = [
Image.fromarray(np.transpose(v, (1, 2, 0))) for v in images_for_item
]
conversation = [
{
"role": "user",
"content": [
*[{"type": "image", "image": img} for img in pil_images],
{"type": "text", "text": language},
],
}
]
text = processor.apply_chat_template(
conversation, tokenize=False, add_generation_prompt=False
)
texts.append(text)
all_images.extend(pil_images)
tokenized = processor(
text=texts,
images=all_images,
return_tensors="pt",
padding=True,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

In _process_vlm_injected, all_images is populated as a flat list of images across all batch items using all_images.extend(pil_images). However, when len(texts) > 1 (batched input), the HuggingFace Qwen2VLProcessor expects images to be a list of lists of images (one list per text prompt). Passing a flat list of images when there are multiple text prompts will cause a ValueError due to a length mismatch. Change all_images to be a list of lists of images by appending pil_images instead of extending.

        all_images: list[list[Image.Image]] = []
        for images_for_item, language in zip(transformed_images, languages):
            pil_images = [
                Image.fromarray(np.transpose(v, (1, 2, 0))) for v in images_for_item
            ]
            conversation = [
                {
                    "role": "user",
                    "content": [
                        *[{"type": "image", "image": img} for img in pil_images],
                        {"type": "text", "text": language},
                    ],
                }
            ]
            text = processor.apply_chat_template(
                conversation, tokenize=False, add_generation_prompt=False
            )
            texts.append(text)
            all_images.append(pil_images)
        tokenized = processor(
            text=texts,
            images=all_images,
            return_tensors="pt",
            padding=True,
        )

Comment on lines +215 to +220
norm_params[embodiment_tag]["action"][key] = {
k: np.asarray(v, dtype=np.float32)
for k, v in relative.items()
if k in ("min", "max", "mean", "std", "q01", "q99")
}
norm_params[embodiment_tag]["action"][key]["dim"] = action_dim

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

In _build_norm_params, when use_relative_action is enabled, the relative action statistics are copied directly from relative.items() without checking use_percentiles. This means "min" and "max" are set to the raw minimum and maximum values, even if use_percentiles is True. For normal actions and states, "min" and "max" are correctly mapped to "q01" and "q99" when use_percentiles is True. Update the relative action normalization parameters to respect use_percentiles as well.

            min_vals = np.asarray(
                relative["q01"] if use_percentiles and "q01" in relative else relative["min"],
                dtype=np.float32,
            )
            max_vals = np.asarray(
                relative["q99"] if use_percentiles and "q99" in relative else relative["max"],
                dtype=np.float32,
            )
            norm_params[embodiment_tag]["action"][key] = {
                "min": min_vals,
                "max": max_vals,
                "mean": np.asarray(relative["mean"], dtype=np.float32),
                "std": np.asarray(relative["std"], dtype=np.float32),
                "dim": action_dim,
            }

Comment on lines +646 to +665
images = [torch.from_numpy(observation.video[key]) for key in image_keys]
stacked = torch.stack(images, dim=2)
if stacked.ndim != 6:
raise ValueError("stacked video must have shape (B,T,V,H,W,C).")
batch, time, views, height, width, channels = stacked.shape
images_flat = stacked.reshape(batch, time * views, height, width, channels)
transformed_images = []
for b in range(batch):
transformed_frames = []
for frame in images_flat[b]:
transformed_frames.append(
eval_transform_image(
frame.numpy(),
shortest_image_edge=self.shortest_image_edge,
image_target_size=self.image_target_size,
image_crop_size=self.image_crop_size,
crop_fraction=self.crop_fraction,
)
)
transformed_images.append(np.stack(transformed_frames, axis=0))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

In _process_vlm, the video frames are converted to PyTorch tensors, stacked, reshaped, and then converted back to numpy arrays via frame.numpy() for image transformation. Since observation.video[key] is already a numpy array, we can perform the stacking and reshaping entirely in numpy. This avoids the unnecessary overhead of PyTorch tensor creation, stacking, and reshaping on the CPU.

Suggested change
images = [torch.from_numpy(observation.video[key]) for key in image_keys]
stacked = torch.stack(images, dim=2)
if stacked.ndim != 6:
raise ValueError("stacked video must have shape (B,T,V,H,W,C).")
batch, time, views, height, width, channels = stacked.shape
images_flat = stacked.reshape(batch, time * views, height, width, channels)
transformed_images = []
for b in range(batch):
transformed_frames = []
for frame in images_flat[b]:
transformed_frames.append(
eval_transform_image(
frame.numpy(),
shortest_image_edge=self.shortest_image_edge,
image_target_size=self.image_target_size,
image_crop_size=self.image_crop_size,
crop_fraction=self.crop_fraction,
)
)
transformed_images.append(np.stack(transformed_frames, axis=0))
images = [observation.video[key] for key in image_keys]
stacked = np.stack(images, axis=2)
if stacked.ndim != 6:
raise ValueError("stacked video must have shape (B,T,V,H,W,C).")
batch, time, views, height, width, channels = stacked.shape
images_flat = stacked.reshape(batch, time * views, height, width, channels)
transformed_images = []
for b in range(batch):
transformed_frames = []
for frame in images_flat[b]:
transformed_frames.append(
eval_transform_image(
frame,
shortest_image_edge=self.shortest_image_edge,
image_target_size=self.image_target_size,
image_crop_size=self.image_crop_size,
crop_fraction=self.crop_fraction,
)
)
transformed_images.append(np.stack(transformed_frames, axis=0))

Comment on lines +168 to +177
x_float = x.float()
mean = x_float.mean(dim=-1, keepdim=True)
var = (x_float - mean).square().mean(dim=-1, keepdim=True)
out = (x_float - mean) * torch.rsqrt(var + eps)
out = out.to(dtype=x.dtype)
if weight is not None:
out = out * weight.to(device=x.device, dtype=x.dtype)
if bias is not None:
out = out + bias.to(device=x.device, dtype=x.dtype)
return out

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The manual implementation of layer normalization in _torch_layer_norm performs multiple passes over the last dimension of the tensor, which is inefficient and uses unnecessary temporary memory. PyTorch provides a highly optimized F.layer_norm operator that is faster, more memory-efficient, and numerically stable on both CPU and GPU. Use F.layer_norm instead.

Suggested change
x_float = x.float()
mean = x_float.mean(dim=-1, keepdim=True)
var = (x_float - mean).square().mean(dim=-1, keepdim=True)
out = (x_float - mean) * torch.rsqrt(var + eps)
out = out.to(dtype=x.dtype)
if weight is not None:
out = out * weight.to(device=x.device, dtype=x.dtype)
if bias is not None:
out = out + bias.to(device=x.device, dtype=x.dtype)
return out
return F.layer_norm(x, (x.shape[-1],), weight, bias, eps)

@yuerqiqi yuerqiqi changed the title Refactor gr00t shared qwen3vl feat(phyai): Refactor gr00t shared qwen3vl Jul 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
phyai-utils-tools/src/phyai_utils_tools/models/gr00t/processor_gr00t.py (1)

348-350: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dead assignments; statistics/embodiment_id_mapping overrides are silently ignored.

kwargs["statistics"] and kwargs["embodiment_id_mapping"] are set but excluded from keep, so they never reach init_kwargs; both values are instead passed explicitly at Lines 378-379. This means a caller-supplied statistics/embodiment_id_mapping in overrides is dropped. Remove the redundant assignments or fold these into the override flow if overriding is intended.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai-utils-tools/src/phyai_utils_tools/models/gr00t/processor_gr00t.py`
around lines 348 - 350, The redundant assignments in the GR00T processor setup
cause caller-provided statistics and embodiment_id_mapping overrides to be
ignored. Update the initialization flow in processor_gr00t.py around the
init_kwargs/overrides handling so these values are either included in the
preserved kwargs path or removed from the separate explicit assignment path,
ensuring overrides passed into the GR00T processor are not silently dropped.
phyai/src/phyai/models/gr00t_n17/qwen3_vl_adapter.py (1)

43-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate layer-truncation logic across two files.

This method mirrors GR00TN17Backbone._truncate_language_layers in modeling_gr00t_n17.py (lines 248-259), which independently re-derives language_model and re-implements the same "pop from end while over budget" loop. Consider extracting a shared helper (e.g., in a small util module) taking a language_model.layers-like ModuleList and select_layer to avoid keeping two divergence-prone copies in sync.

♻️ Example shared helper
def truncate_language_layers(layers: nn.ModuleList, select_layer: int) -> None:
    if select_layer < 0:
        return
    while len(layers) > select_layer:
        layers.pop(-1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai/src/phyai/models/gr00t_n17/qwen3_vl_adapter.py` around lines 43 - 48,
The layer truncation logic is duplicated between
qwen3_vl_adapter._truncate_language_layers and
GR00TN17Backbone._truncate_language_layers, so extract the shared “truncate from
end while over select_layer” behavior into a common helper and have both call
it. Use the existing language_model.layers ModuleList and select_layer parameter
in the helper, and update both _truncate_language_layers methods to delegate
instead of re-implementing the loop.
phyai/src/phyai/models/gr00t_n17/modeling_gr00t_n17.py (2)

460-542: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer torch.nn.init over hand-rolled initializers.

_calculate_fan_in_and_fan_out, _init_kaiming_uniform_, _init_uniform_, _init_normal_, and _init_zeros_ re-implement torch.nn.init.kaiming_uniform_/uniform_/normal_/zeros_. The math matches PyTorch's implementation, but maintaining a parallel copy of well-tested initializer formulas is unnecessary risk for future edits.

♻️ Example simplification
-def _init_kaiming_uniform_(tensor: torch.Tensor, a: float) -> None:
-    fan_in, _ = _calculate_fan_in_and_fan_out(tensor)
-    gain = math.sqrt(2.0 / (1.0 + a**2))
-    std = gain / math.sqrt(fan_in)
-    bound = math.sqrt(3.0) * std
-    with torch.no_grad():
-        tensor.uniform_(-bound, bound)
+def _init_kaiming_uniform_(tensor: torch.Tensor, a: float) -> None:
+    nn.init.kaiming_uniform_(tensor, a=a)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai/src/phyai/models/gr00t_n17/modeling_gr00t_n17.py` around lines 460 -
542, The custom initializer helpers in GR00TN17Linear duplicate torch.nn.init
behavior and should be replaced with the standard PyTorch init utilities. Update
the parameter setup in GR00TN17Linear.reset_parameters to use torch.nn.init for
kaiming_uniform_, uniform_, normal_, and zeros_ instead of the local helpers,
and remove the redundant helper functions such as _calculate_fan_in_and_fan_out,
_init_kaiming_uniform_, _init_uniform_, _init_normal_, and _init_zeros_ if they
are no longer used.

265-292: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

vision_attention_backend isn't wired to config; always resolves to the adapter's hardcoded default.

_build_qwen3vl_model passes attention_backend=self.config.attention_backend but never a vision_attention_backend, so the vision backbone always uses GR00TN17Qwen3VLBackbone's default ("flashinfer") regardless of GR00TN17BackboneConfig. If this is intentional (vision always flashinfer), consider a brief comment; otherwise expose a config knob so it's consistent with how attention_backend is already configurable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai/src/phyai/models/gr00t_n17/modeling_gr00t_n17.py` around lines 265 -
292, The vision attention backend is not configurable in _build_qwen3vl_model,
so GR00TN17Qwen3VLBackbone always falls back to its default instead of using
GR00TN17BackboneConfig. Update the GR00TN17 configuration and the
_build_qwen3vl_model path to pass an explicit vision_attention_backend through
to GR00TN17Qwen3VLBackbone, similar to how attention_backend is already wired,
or add a short comment if the default is intentionally fixed.
phyai/src/phyai/models/gr00t_n17/model_runner_gr00t_n17.py (1)

265-352: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Fast path duplicates and tightly couples to Qwen3-VL internals.

_backbone_core re-derives the full ViT→merger→deepstack→LLM pipeline by reaching into qwen_model.visual/qwen_model.language_model internals instead of the model's own forward. This is understandable for CUDA-graph capture, but it means any future change to the shared Qwen3-VL vision/LLM forward (e.g., in modeling_qwen3_vl.py) must be manually mirrored here or the graph path will silently diverge from the eager path. Consider adding a regression/parity test asserting graph-path vs eager-path output equivalence, and/or a comment pointing back to the source-of-truth forward method(s) it must stay in sync with.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai/src/phyai/models/gr00t_n17/model_runner_gr00t_n17.py` around lines 265
- 352, The `_backbone_core` fast path is duplicating Qwen3-VL’s vision/LLM
forward logic and can drift from the eager path. Add a parity/regression test
that compares this path against the source-of-truth forward behavior, and add a
clear sync note near `_backbone_core` referencing the `qwen_model.visual` and
`qwen_model.language_model` flow so future changes in the shared forward
implementation are mirrored here.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@phyai-utils-tools/src/phyai_utils_tools/models/gr00t/ops_gr00t.py`:
- Around line 149-173: The GR00T eval transform in op_gr00t.py has a silent PIL
fallback that can produce different pixels than the cv2 path, so make the
runtime dependency explicit or document the behavior. Update the package
manifest to include opencv-python for the image transform path used by
_eval_transform_image, or if the fallback must remain, clearly mark
_eval_transform_image_cv2 and the PIL branch as non-identical so users know eval
results may vary across environments.

In `@phyai/src/phyai/models/gr00t_n17/main_gr00t_n17.py`:
- Around line 137-144: Remove the dead no-op call to the private method on the
backbone before loading weights. In the checkpoint-loading branch of
main_gr00t_n17.py, delete the self.model.backbone._load_qwen3vl_model() call and
keep only the load_pretrained flow, unless you intended a distinct pre-load
step; if so, replace it with an explicit public action instead of reaching into
the _load_qwen3vl_model helper.

In `@phyai/src/phyai/models/gr00t_n17/qwen3_vl_adapter.py`:
- Around line 22-41: The Qwen3VL adapter constructor is hard-coding the text
attention backend and bypassing shared backend resolution. Update
Qwen3VLAdapter.__init__ so it does not force attention_backend="sdpa"; instead
pass through None or the engine-configured value when calling the
Qwen3VLForConditionalGeneration parent init, while keeping the vision backend
behavior unchanged.

---

Nitpick comments:
In `@phyai-utils-tools/src/phyai_utils_tools/models/gr00t/processor_gr00t.py`:
- Around line 348-350: The redundant assignments in the GR00T processor setup
cause caller-provided statistics and embodiment_id_mapping overrides to be
ignored. Update the initialization flow in processor_gr00t.py around the
init_kwargs/overrides handling so these values are either included in the
preserved kwargs path or removed from the separate explicit assignment path,
ensuring overrides passed into the GR00T processor are not silently dropped.

In `@phyai/src/phyai/models/gr00t_n17/model_runner_gr00t_n17.py`:
- Around line 265-352: The `_backbone_core` fast path is duplicating Qwen3-VL’s
vision/LLM forward logic and can drift from the eager path. Add a
parity/regression test that compares this path against the source-of-truth
forward behavior, and add a clear sync note near `_backbone_core` referencing
the `qwen_model.visual` and `qwen_model.language_model` flow so future changes
in the shared forward implementation are mirrored here.

In `@phyai/src/phyai/models/gr00t_n17/modeling_gr00t_n17.py`:
- Around line 460-542: The custom initializer helpers in GR00TN17Linear
duplicate torch.nn.init behavior and should be replaced with the standard
PyTorch init utilities. Update the parameter setup in
GR00TN17Linear.reset_parameters to use torch.nn.init for kaiming_uniform_,
uniform_, normal_, and zeros_ instead of the local helpers, and remove the
redundant helper functions such as _calculate_fan_in_and_fan_out,
_init_kaiming_uniform_, _init_uniform_, _init_normal_, and _init_zeros_ if they
are no longer used.
- Around line 265-292: The vision attention backend is not configurable in
_build_qwen3vl_model, so GR00TN17Qwen3VLBackbone always falls back to its
default instead of using GR00TN17BackboneConfig. Update the GR00TN17
configuration and the _build_qwen3vl_model path to pass an explicit
vision_attention_backend through to GR00TN17Qwen3VLBackbone, similar to how
attention_backend is already wired, or add a short comment if the default is
intentionally fixed.

In `@phyai/src/phyai/models/gr00t_n17/qwen3_vl_adapter.py`:
- Around line 43-48: The layer truncation logic is duplicated between
qwen3_vl_adapter._truncate_language_layers and
GR00TN17Backbone._truncate_language_layers, so extract the shared “truncate from
end while over select_layer” behavior into a common helper and have both call
it. Use the existing language_model.layers ModuleList and select_layer parameter
in the helper, and update both _truncate_language_layers methods to delegate
instead of re-implementing the loop.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b6b50832-b38d-4dde-a3b4-0dc39024de16

📥 Commits

Reviewing files that changed from the base of the PR and between 1644b93 and f044fcd.

📒 Files selected for processing (14)
  • CLAUDE.md
  • phyai-utils-tools/pyproject.toml
  • phyai-utils-tools/src/phyai_utils_tools/models/gr00t/__init__.py
  • phyai-utils-tools/src/phyai_utils_tools/models/gr00t/ops_gr00t.py
  • phyai-utils-tools/src/phyai_utils_tools/models/gr00t/processor_gr00t.py
  • phyai/src/phyai/engine.py
  • phyai/src/phyai/models/gr00t_n17/__init__.py
  • phyai/src/phyai/models/gr00t_n17/configuration_gr00t_n17.py
  • phyai/src/phyai/models/gr00t_n17/main_gr00t_n17.py
  • phyai/src/phyai/models/gr00t_n17/model_runner_gr00t_n17.py
  • phyai/src/phyai/models/gr00t_n17/modeling_gr00t_n17.py
  • phyai/src/phyai/models/gr00t_n17/qwen3_vl_adapter.py
  • phyai/src/phyai/models/gr00t_n17/scheduler_ws1_gr00t_n17.py
  • phyai/src/phyai/models/qwen3_vl/modeling_qwen3_vl.py

Comment thread phyai-utils-tools/src/phyai_utils_tools/models/gr00t/ops_gr00t.py
Comment thread phyai/src/phyai/models/gr00t_n17/main_gr00t_n17.py
Comment on lines +22 to +41
def __init__(
self,
config: Qwen3VLConfig,
*,
select_layer: int,
params_dtype: torch.dtype | None = None,
device: torch.device | str | None = None,
attention_backend: str = "sdpa",
vision_attention_backend: str = "flashinfer",
) -> None:
super().__init__(
config,
params_dtype=params_dtype,
device=device,
attn_backend=attention_backend,
vision_attn_backend=vision_attention_backend,
prefix="backbone.model",
)
self.select_layer = int(select_layer)
self._truncate_language_layers()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "MagiAttention|resolve_attn_backend|\bCP\b" phyai/src/phyai/layers -C3
rg -n "attn_backend|vision_attn_backend" phyai/src/phyai/models/qwen3_vl/modeling_qwen3_vl.py -C3

Repository: MEmbodied/phyai

Length of output: 5628


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant files first
ast-grep outline phyai/src/phyai/models/gr00t_n17/qwen3_vl_adapter.py --view expanded
ast-grep outline phyai/src/phyai/models/qwen3_vl/modeling_qwen3_vl.py --view expanded

# Inspect the adapter and the parent constructor around backend wiring
sed -n '1,140p' phyai/src/phyai/models/gr00t_n17/qwen3_vl_adapter.py
printf '\n--- parent model ---\n'
sed -n '680,760p' phyai/src/phyai/models/qwen3_vl/modeling_qwen3_vl.py
printf '\n--- engine defaults ---\n'
rg -n "def resolve_engine_defaults|CP|MagiAttention|flashinfer|sdpa" phyai/src/phyai -C3

Repository: MEmbodied/phyai

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the default-resolution path and CP/MagiAttention wiring
sed -n '608,660p' phyai/src/phyai/engine_config.py
printf '\n--- MagiAttention references ---\n'
rg -n "MagiAttention|cp_size|context parallel|attn_backend.*sdpa|vision_attention_backend" phyai/src/phyai -C3

printf '\n--- GR00T adapter usages ---\n'
rg -n "GR00TN17Qwen3VLBackbone|qwen3_vl_adapter" phyai/src/phyai -C3

Repository: MEmbodied/phyai

Length of output: 10517


Stop pinning the text attention backend here
Qwen3VLForConditionalGeneration only applies engine defaults when an arg is None; passing attention_backend="sdpa" here bypasses the shared flashinfer default on CUDA and any CP-driven backend switch. Pass None (or thread through the engine config) so this adapter inherits the normal backend resolution.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai/src/phyai/models/gr00t_n17/qwen3_vl_adapter.py` around lines 22 - 41,
The Qwen3VL adapter constructor is hard-coding the text attention backend and
bypassing shared backend resolution. Update Qwen3VLAdapter.__init__ so it does
not force attention_backend="sdpa"; instead pass through None or the
engine-configured value when calling the Qwen3VLForConditionalGeneration parent
init, while keeping the vision backend behavior unchanged.

Source: Coding guidelines

@yuerqiqi yuerqiqi changed the title feat(phyai): Refactor gr00t shared qwen3vl feat(phyai):Adds GR00T-N1.7 model support Jul 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
phyai-utils-tools/pyproject.toml (2)

17-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Verify fastokens version constraint is intentional.

fastokens>=0.2.0 uses an open-ended lower bound; confirm this is intentional versus pinning an exact/known-good version to avoid unexpected breakage from future releases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai-utils-tools/pyproject.toml` around lines 17 - 20, The fastokens
dependency in the optional-dependencies section uses an open-ended minimum
version, so confirm the intended release policy and update the fastokenizer
requirement accordingly. If you want a stable known-good dependency, change the
constraint to an exact or bounded version; otherwise keep the lower bound as-is,
but make the choice explicit in pyproject.toml near fastokenizer.

10-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider pinning new dependency versions.

New deps (opencv-python-headless, pillow, torchvision) appear to be added without explicit version pins, unlike torch==2.11 and transformers==5.8.1 in the same list. For reproducible builds, especially with torchvision which must stay compatible with the pinned torch==2.11, consider adding explicit or range-bounded pins.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@phyai-utils-tools/pyproject.toml` around lines 10 - 15, The dependency list
in pyproject.toml adds opencv-python-headless, pillow, and torchvision without
version constraints, which can break reproducible builds and torch
compatibility. Update the dependency entries in the same list to use explicit
pins or safe version ranges, and make sure torchvision is constrained to a
version compatible with the existing torch==2.11 pin. Keep the existing style
consistent with the other pinned dependencies like torch and transformers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@phyai-utils-tools/pyproject.toml`:
- Around line 17-20: The fastokens dependency in the optional-dependencies
section uses an open-ended minimum version, so confirm the intended release
policy and update the fastokenizer requirement accordingly. If you want a stable
known-good dependency, change the constraint to an exact or bounded version;
otherwise keep the lower bound as-is, but make the choice explicit in
pyproject.toml near fastokenizer.
- Around line 10-15: The dependency list in pyproject.toml adds
opencv-python-headless, pillow, and torchvision without version constraints,
which can break reproducible builds and torch compatibility. Update the
dependency entries in the same list to use explicit pins or safe version ranges,
and make sure torchvision is constrained to a version compatible with the
existing torch==2.11 pin. Keep the existing style consistent with the other
pinned dependencies like torch and transformers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a271b9f-9ed0-40d7-a172-16e2112f7efa

📥 Commits

Reviewing files that changed from the base of the PR and between f044fcd and 5f33879.

📒 Files selected for processing (9)
  • phyai-utils-tools/pyproject.toml
  • phyai-utils-tools/src/phyai_utils_tools/models/gr00t/ops_gr00t.py
  • phyai-utils-tools/src/phyai_utils_tools/models/gr00t/processor_gr00t.py
  • phyai/src/phyai/models/gr00t_n17/__init__.py
  • phyai/src/phyai/models/gr00t_n17/configuration_gr00t_n17.py
  • phyai/src/phyai/models/gr00t_n17/main_gr00t_n17.py
  • phyai/src/phyai/models/gr00t_n17/model_runner_gr00t_n17.py
  • phyai/src/phyai/models/gr00t_n17/modeling_gr00t_n17.py
  • phyai/src/phyai/models/gr00t_n17/scheduler_ws1_gr00t_n17.py
💤 Files with no reviewable changes (2)
  • phyai/src/phyai/models/gr00t_n17/init.py
  • phyai/src/phyai/models/gr00t_n17/main_gr00t_n17.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • phyai/src/phyai/models/gr00t_n17/scheduler_ws1_gr00t_n17.py
  • phyai-utils-tools/src/phyai_utils_tools/models/gr00t/processor_gr00t.py
  • phyai/src/phyai/models/gr00t_n17/model_runner_gr00t_n17.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant