Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for LoRA-only training and serving of IQuest LoopCoder models using Megatron-Core and vLLM. It adds a vLLM patch to enable LoRA capability, implements a custom two-pass Megatron-Core transformer block and attention mechanism to reuse physical layer weights, and integrates these components into the Megatron worker and model bridges. Feedback on the changes highlights a critical shape mismatch in the loop gate projection, a bug in the worker configuration check that would break PPO/RLHF reference workers, and an opportunity to optimize GPU memory usage by clearing captured key-value tensors after the second attention pass.
| ) | ||
| gate = torch.diagonal(gate_logits, dim1=-2, dim2=-1).unsqueeze(-1) | ||
| gate = torch.sigmoid(gate.float()).to(query.dtype) | ||
| return gate.expand(*gate.shape[:-1], self.head_dim).reshape(*query.shape[:-2], -1) |
There was a problem hiding this comment.
There is a shape mismatch bug here. In Megatron-Core, core_attention modules (such as DotProductAttention) return attention context tensors of shape [..., num_heads, head_dim] (e.g., [seq_len, batch_size, num_heads, head_dim]). However, the current implementation reshapes the gate to [..., num_heads * head_dim], which will cause a RuntimeError due to shape mismatch when performing element-wise multiplication with local_output and global_output in LoopCoderCoreAttention.forward.
Removing the .reshape(*query.shape[:-2], -1) call ensures the gate retains the correct [..., num_heads, head_dim] shape matching the attention outputs.
| return gate.expand(*gate.shape[:-1], self.head_dim).reshape(*query.shape[:-2], -1) | |
| return gate.expand(*gate.shape[:-1], self.head_dim) |
| if getattr(hf_config_original, "model_type", None) == "iquestloopcoder": | ||
| if lora_config is None or lora_config.rank <= 0: | ||
| raise ValueError("IQuest LoopCoder is supported only for LoRA training") | ||
| if self.cfg.policy.megatron_config.lora_config.merge_lora: | ||
| raise ValueError( | ||
| "IQuest LoopCoder requires policy.megatron_config.lora_config.merge_lora=false " | ||
| "so the shared physical-layer adapters are served through vLLM's LoRA path" | ||
| ) |
There was a problem hiding this comment.
This check will completely break PPO/RLHF training for IQuest LoopCoder. During training, a reference worker (MegatronRefWorkerBase) is initialized to compute reference logprobs. The reference worker runs the frozen base model and does not use LoRA adapters, so its lora_config is None. Because this check is enforced globally for any worker initializing an iquestloopcoder model, it will raise a ValueError when initializing the reference worker.
Additionally, using self.cfg.policy.megatron_config directly is incorrect because the reference worker uses self.cfg.ref.megatron_config. We should use the passed megatron_config argument instead.
We should restrict this check to only run for the policy worker (where training actually occurs) by checking isinstance(self, PolicyWorkerBase).
| if getattr(hf_config_original, "model_type", None) == "iquestloopcoder": | |
| if lora_config is None or lora_config.rank <= 0: | |
| raise ValueError("IQuest LoopCoder is supported only for LoRA training") | |
| if self.cfg.policy.megatron_config.lora_config.merge_lora: | |
| raise ValueError( | |
| "IQuest LoopCoder requires policy.megatron_config.lora_config.merge_lora=false " | |
| "so the shared physical-layer adapters are served through vLLM's LoRA path" | |
| ) | |
| if getattr(hf_config_original, "model_type", None) == "iquestloopcoder" and isinstance(self, PolicyWorkerBase): | |
| if lora_config is None or lora_config.rank <= 0: | |
| raise ValueError("IQuest LoopCoder is supported only for LoRA training") | |
| if megatron_config.lora_config.merge_lora: | |
| raise ValueError( | |
| "IQuest LoopCoder requires policy.megatron_config.lora_config.merge_lora=false " | |
| "so the shared physical-layer adapters are served through vLLM's LoRA path" | |
| ) |
| global_output = self.global_attention( | ||
| query, | ||
| self._shared_key, | ||
| self._shared_value, | ||
| attention_mask, | ||
| **kwargs, | ||
| ) | ||
| local_output = self.local_attention(query, key, value, attention_mask, **kwargs) | ||
| gate = self.gate_projection(query) | ||
| return local_output * (1.0 - gate) + global_output * gate |
There was a problem hiding this comment.
The attributes self._shared_key and self._shared_value store references to the first-pass key and value tensors. Because these are stored as instance attributes on LoopCoderCoreAttention, they will remain in GPU memory even after the forward and backward passes of the current training step are completed. This can lead to unnecessary memory retention and potential fragmentation.
Since PyTorch's autograd automatically captures and saves any tensors needed for the backward pass during the forward execution of global_attention, we can safely clear these references at the end of the second-pass forward method to free up GPU memory.
| global_output = self.global_attention( | |
| query, | |
| self._shared_key, | |
| self._shared_value, | |
| attention_mask, | |
| **kwargs, | |
| ) | |
| local_output = self.local_attention(query, key, value, attention_mask, **kwargs) | |
| gate = self.gate_projection(query) | |
| return local_output * (1.0 - gate) + global_output * gate | |
| global_output = self.global_attention( | |
| query, | |
| self._shared_key, | |
| self._shared_value, | |
| attention_mask, | |
| **kwargs, | |
| ) | |
| self._shared_key = None | |
| self._shared_value = None | |
| local_output = self.local_attention(query, key, value, attention_mask, **kwargs) | |
| gate = self.gate_projection(query) | |
| return local_output * (1.0 - gate) + global_output * gate |
|
| return query.new_full((*query.shape[:-2], query.shape[-2] * query.shape[-1]), 0.25) | ||
|
|
||
|
|
||
| def test_loopcoder_core_attention_reuses_first_pass_kv_with_gradients(): |
There was a problem hiding this comment.
This adds new Megatron model support, but the tests only cover LoRA protocol declarations and isolated attention mixing. The repository's new-model checklist requires a generation-and-logprobs test comparing Hugging Face and Megatron behavior. Without that test, checkpoint mapping, recurrent model fidelity, and training-to-inference weight synchronization remain untested. This repository requirement must be satisfied before merging; add an IQuest case to the Megatron model test matrix, using a tiny checkpoint if needed.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| @MegatronModelBridge.register_bridge( | ||
| source="IQuestLoopCoderForCausalLM", | ||
| target=GPTModel, | ||
| provider=LoopCoderProvider, | ||
| model_type="iquestloopcoder", | ||
| ) |
There was a problem hiding this comment.
Missing model training example
This registers IQuest LoopCoder as a new Megatron model without the README and training script required by the repository's new-model checklist. This requirement must be satisfied before merging. Add a runnable examples/train/<model>/ example that documents the LoRA-only, unmerged-adapter, two-loop, and pipeline-parallel-size-one configuration.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 52fd627. Configure here.
| skip_bias_add=False, | ||
| tp_group=pg_collection.tp, | ||
| name=(name + ".gate_proj") if name is not None else None, | ||
| ) |
There was a problem hiding this comment.
Gate layer mishandles sequence parallel
High Severity
LoopGateProjection builds ColumnParallelLinear from the shared transformer config, so sequence_parallel stays enabled whenever TP is greater than 1. Queries reaching core attention are already full-sequence, and that extra dim-0 all-gather reshapes the gate to tp times the local hidden width. Loop-2 mixing then fails on the intended 40B path; the new tests never construct this layer.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 52fd627. Configure here.


Testing
5 tests passed, covering vLLM LoRA capability detection and recurrent KV mixing/gradients.
Ruff, Black, and hardcoded-secret checks passed. AutoBridge dispatch was also verified against
IQuestLab/IQuest-Coder-V1-40B-Loop-Instruct; full 40B GPU training/serving was not run locally.Note
Medium Risk
New recurrent attention training path with strict recompute/LoRA config requirements; mistakes could break gradients or inference adapter loading, but scope is limited to LoRA and a single model family.
Overview
Adds LoRA-only support for IQuest LoopCoder across Megatron training and vLLM serving.
On inference, a startup patch marks vLLM’s native
IQuestLoopCoderForCausalLMas LoRA-capable (QKV + gate/up packed mappings) and is wired into the vLLM worker bootstrap alongside the existing Kimi patch.On training, a new Megatron path runs the decoder twice with shared weights: pass 1 does full attention and caches KV; pass 2 blends that global attention with sliding-window local attention via a frozen per-head gate. A Megatron-Bridge registration maps HF LoopCoder weights (including gate projections) into this stack.
Megatron worker changes enforce LoopCoder constraints: LoRA with
merge_lora=false, auto-downgrade full activation recompute to MLP-only selective recompute (to preserve loop-1 KV gradients), and provider-side guards on PP=1 and recompute settings.Tests cover the vLLM LoRA patch and recurrent KV mixing/backprop behavior.
Reviewed by Cursor Bugbot for commit 52fd627. Bugbot is set up for automated code reviews on this repo. Configure here.