Skip to content

[megatron] 12/n towards Kimi K2.6: harden AdapterStore swaps for Tinker API multi-LoRA session churn - #2063

Closed
casper-hansen wants to merge 1 commit into
NovaSky-AI:mainfrom
casper-hansen:casper/kimi-12-adapter-store-hardening
Closed

[megatron] 12/n towards Kimi K2.6: harden AdapterStore swaps for Tinker API multi-LoRA session churn#2063
casper-hansen wants to merge 1 commit into
NovaSky-AI:mainfrom
casper-hansen:casper/kimi-12-adapter-store-hardening

Conversation

@casper-hansen

Copy link
Copy Markdown
Contributor

Part of the Kimi K2.6/K2.7 series (previous: #2062). Standalone: fixes apply to the existing multi-LoRA AdapterStore on main. Pairs naturally with #2062 (offload) and #2026 (merge_lora=false sync), which make the fixed code paths much hotter.

What

Three fixes to AdapterStore, plus a CPU-side unit-test suite (test_adapter_store_swap.py) that fakes the DDP buffers via a monkeypatched _iter_buffers and replays the exact production ordering (create new model -> expiry-delete of the current one -> swap with grads offloaded):

  • _snapshot/_restore skip grad_data copies while the DDP grad buffers are offloaded: storage().resize_(0) leaves a stale view behind, and reading/writing it is UB. Megatron zero-fills grad buffers on reload anyway, and grads are only ever offloaded post-step, so recording zeros is faithful.
  • create() no longer adopts the live GPU state when current_id was cleared by delete(): a new _live_dirty flag distinguishes "live is pristine" (true first create) from "live still mirrors a deleted tenant" (must seed from the pristine snapshot; swap_to restores it).
  • register_pristine() synchronizes after its non_blocking D2H snapshot so create()'s CPU-side _copy_slot cannot race the in-flight DMA.

Why

Long-running Tinker API service with multiple LoRA sessions churning (clients create models, train, hit session expiry, reconnect) on our Kimi K2.7 B300 deployment. On 2026-08-18 we hit the failure chain this PR fixes: a swap_to_adapter crashed with CUDA invalid argument at the grad copy_ (grad buffers had been offloaded post-step, leaving resized-to-zero storages), the run restarted, and the retry then silently inherited the previous tenant's live adapter state because create() treated the leftover live weights as pristine after the expiry delete() had cleared current_id. The second bug is the nastier one — no crash, just a new session fine-tuning on top of a dead tenant's weights.

Made with Cursor

…deleted-tenant live state

Three fixes for the session-churn crash seen on 2026-08-18 (swap_to_adapter
-> CUDA invalid argument at grad copy_, then silent state inheritance on the
retry run):

- _snapshot/_restore skip grad_data copies while the DDP grad buffers are
  offloaded (storage().resize_(0) leaves a stale view; Megatron zero-fills
  on reload anyway, and grads are only offloaded post-step). Snapshot
  records zeros instead of reading freed memory.
- create() no longer adopts the live GPU state when current_id was cleared
  by delete(): a new _live_dirty flag distinguishes "live is pristine"
  (true first create) from "live mirrors a deleted tenant" (must seed from
  pristine, swap_to restores it).
- register_pristine() synchronizes after its non_blocking D2H snapshot so
  create()'s CPU-side _copy_slot can't race the in-flight DMA.

Unit tests fake the DDP buffers via monkeypatched _iter_buffers and cover
the exact production ordering (create new model -> expiry-delete of current
-> swap with grads offloaded).

Co-authored-by: Cursor <cursoragent@cursor.com>

@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 addresses two production issues in AdapterStore for Megatron: it prevents crashes during swap_to() when DDP gradient buffers are offloaded by skipping gradient copies when storage size is zero, and introduces a _live_dirty flag to prevent newly created adapters from adopting the live GPU state of a deleted adapter. Unit tests have been added to cover these scenarios. The review feedback suggests defensively guarding against buf.grad_data being None or missing in _grad_data_live to prevent potential AttributeError crashes.

Comment on lines +56 to +57
gd = buf.grad_data
return not (gd.is_cuda and gd.untyped_storage().size() == 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

Defensively guard against buf.grad_data being None. While Megatron currently offloads gradients by resizing the storage to 0, future versions or alternative DDP implementations might set grad_data to None directly. Checking for None prevents potential AttributeError crashes.

Suggested change
gd = buf.grad_data
return not (gd.is_cuda and gd.untyped_storage().size() == 0)
gd = getattr(buf, "grad_data", None)
if gd is None:
return False
return not (gd.is_cuda and gd.untyped_storage().size() == 0)

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit e9a3a7c. Configure here.

# else: grad storage is offloaded/freed. zero_grad_buffer() /
# restore_grad_buffers() reallocate it zero-filled before the next
# forward_backward, and a slot swapped in while offloaded carries
# post-step (zero) grads anyway — skipping loses nothing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Offloaded swap drops pending adapter grads

High Severity

_restore skips writing a slot's grad_data while DDP grad storage is offloaded, then swap_to still marks that adapter current. A later forward_backward backloads grads as zeros and the swap is a no-op, so pending CPU grads never land. forward() already swaps with the optimizer still offloaded, so a tenant that accumulated, got swapped away, then came back via sample/forward can silently drop interrupted forward_backward grads.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e9a3a7c. Configure here.

@erictang000

Copy link
Copy Markdown
Collaborator

hey @casper-hansen i think this should be solved by #2000, closing this for now, feel free to re-open if #2000 looks insufficient

@erictang000 erictang000 closed this Sep 3, 2026
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.

2 participants