@@ -373,7 +373,7 @@ def test_model_engine_delegates_ragged_state_to_decoder_owner() -> None:
373373 inputs ._dspark_exact_identity_words = tuple (range (8 ))
374374 inputs .fit_ragged_verify_lens = lambda * args , ** kwargs : (args , kwargs )
375375 worker = object ()
376- runner .model = SimpleNamespace (spec_worker = worker )
376+ runner ._model = SimpleNamespace (spec_worker = worker )
377377 engine = object .__new__ (PyTorchModelEngine )
378378 engine ._cleanup_done = True
379379 engine ._runner = runner
@@ -440,7 +440,7 @@ def __init__(self, **kwargs):
440440 metadata_module .build_attention_metadata (model , ** kwargs )
441441
442442 assert "enable_ragged_verification" not in seen [0 ]
443- assert seen [0 ]["num_heads_per_kv " ] == 4
443+ assert seen [0 ]["max_num_heads_per_kv " ] == 4
444444
445445
446446def test_metadata_helper_enables_only_supported_ragged_metadata ():
@@ -481,6 +481,7 @@ def __init__(self, **kwargs):
481481 ** kwargs , spec_config = SimpleNamespace (enable_confidence_scheduling = enabled )
482482 )
483483 decoder .mapping = kwargs ["mapping" ]
484+ decoder ._attention_runtime_features = kwargs ["attention_runtime_features" ]
484485 decoder ._buffers = SimpleNamespace (cache_indirection = None )
485486 monkeypatch .setattr (decoder_metadata_module , "resolve_mamba_metadata_cls" , lambda _model : None )
486487 kv = object ()
@@ -498,7 +499,7 @@ def __init__(self, **kwargs):
498499@pytest .mark .parametrize ("graph_type" , [CUDAGraphRunner , DSparkCUDAGraphRunner ])
499500def test_current_main_warmup_keeps_serving_timeout_only_for_capture (monkeypatch , graph_type ):
500501 graph = graph_type .__new__ (graph_type )
501- graph .clear = lambda : None
502+ graph .clear = lambda ** _kwargs : None
502503 graph ._capture_allowed = False
503504 graph .padding_dummy_requests = {5 : object ()}
504505 if graph_type is DSparkCUDAGraphRunner :
@@ -569,12 +570,16 @@ def test_confidence_without_an_sps_table_is_eager_only() -> None:
569570 graphs ._get_graphs_to_capture ([1 , 4 ])
570571
571572
573+ @pytest .mark .parametrize ("use_fused_gather" , [False , True ])
572574@pytest .mark .parametrize (
573575 ("trims" , "prefix_window" , "previous_windows" ),
574576 [(True , 1 , [3 , 3 ]), (True , 1 , [1 , 2 ]), (False , None , [None , None ])],
575577)
576578def test_overlap_offsets_follow_the_packed_prefix (
577- trims : bool , prefix_window : int | None , previous_windows : list [int | None ]
579+ trims : bool ,
580+ prefix_window : int | None ,
581+ previous_windows : list [int | None ],
582+ use_fused_gather : bool ,
578583) -> None :
579584 """Exercise the full input packer with CPU buffers and metadata-only leaves."""
580585 decoder = object .__new__ (DecoderInputPreparer )
@@ -618,6 +623,27 @@ def test_overlap_offsets_follow_the_packed_prefix(
618623 decoder ._mm_item_scheduler = None
619624 decoder ._pinned_host_cache = {}
620625 decoder ._pinned_host_active = {}
626+ decoder ._staged_previous_batch_indices = None
627+ decoder ._staged_previous_pos_indices = None
628+ gather_calls = []
629+
630+ def gather (* args ) -> bool :
631+ # A CPU kernel leaf verifies the production caller's packed offsets.
632+ new , draft , lens , slots , positions , rows , width , draft_width = args [:8 ]
633+ ids , input_begin , drafts , draft_begin , pos , pos_begin , kv , kv_begin = args [8 :]
634+ selected_slots = slots [:rows ].long ()
635+ gather_calls .append ((input_begin , pos_begin , kv_begin ))
636+ ids [input_begin : input_begin + rows * width ].copy_ (
637+ new .transpose (0 , 1 )[selected_slots , :width ].flatten ()
638+ )
639+ drafts [draft_begin : draft_begin + rows * draft_width ].copy_ (
640+ draft [selected_slots , :draft_width ].flatten ()
641+ )
642+ pos [pos_begin : pos_begin + rows * width ].copy_ (lens [positions [: rows * width ].long ()])
643+ kv [kv_begin : kv_begin + rows ].copy_ (lens [selected_slots ] - width )
644+ return True
645+
646+ decoder ._step_input_gather = SimpleNamespace (gather = gather ) if use_fused_gather else None
621647 decoder ._ragged_gather_indices = lambda slots , lengths : tuple (
622648 torch .tensor (indices , dtype = torch .long )
623649 for indices in ragged_gather_index_lists (slots , lengths )
@@ -626,7 +652,6 @@ def test_overlap_offsets_follow_the_packed_prefix(
626652 for name in (
627653 "input_ids_cuda" ,
628654 "draft_tokens_cuda" ,
629- "num_accepted_draft_tokens_cuda" ,
630655 "previous_pos_id_offsets_cuda" ,
631656 "previous_kv_lens_offsets_cuda" ,
632657 "previous_pos_indices_cuda" ,
@@ -635,6 +660,7 @@ def test_overlap_offsets_follow_the_packed_prefix(
635660 "gather_ids_cuda" ,
636661 ):
637662 setattr (decoder ._buffers , name , torch .full ((32 ,), - 99 , dtype = torch .int32 ))
663+ decoder ._gather_ids_buffer = decoder_module .DeviceCopyBuffer (decoder ._buffers .gather_ids_cuda )
638664 windows = [prefix_window , * previous_windows ]
639665 requests = [
640666 SimpleNamespace (
@@ -699,3 +725,87 @@ def test_overlap_offsets_follow_the_packed_prefix(
699725 assert metadata .kv_cache_params .num_extra_kv_tokens == 2
700726 assert gather_ids .tolist () == list (range (sum (lengths )))
701727 assert draft_len == 3
728+ assert gather_calls == (
729+ [(prefix_len , prefix_len , 1 )] if use_fused_gather and previous_lengths == [4 , 4 ] else []
730+ )
731+
732+
733+ @pytest .mark .parametrize ("trims" , [False , True ])
734+ def test_graph_overlap_offset_zeroing_preserves_ragged_bucket_padding (trims : bool ) -> None :
735+ decoder = object .__new__ (DecoderInputPreparer )
736+ decoder ._config = SimpleNamespace (
737+ max_num_tokens = 16 , max_beam_width = 1 , disable_overlap_scheduler = False
738+ )
739+ decoder ._dspark_trims_submitted_tokens = trims
740+ decoder ._staged_previous_batch_indices = None
741+ decoder ._staged_previous_pos_indices = None
742+ decoder ._step_input_gather = None
743+ decoder .get_runtime_tokens_per_gen_step = lambda _length : 4
744+ decoder ._ragged_gather_indices = lambda slots , lengths : tuple (
745+ torch .tensor (indices , dtype = torch .long )
746+ for indices in ragged_gather_index_lists (slots , lengths )
747+ )
748+ decoder ._buffers = SimpleNamespace (
749+ ** {
750+ name : torch .full ((16 ,), - 99 , dtype = torch .int32 )
751+ for name in (
752+ "input_ids_cuda" ,
753+ "draft_tokens_cuda" ,
754+ "previous_batch_indices_cuda" ,
755+ "previous_pos_indices_cuda" ,
756+ "previous_pos_id_offsets_cuda" ,
757+ "previous_kv_lens_offsets_cuda" ,
758+ )
759+ }
760+ )
761+ lengths = [1 , 2 ] if trims else [4 , 4 ]
762+ host = SimpleNamespace (
763+ lists = SimpleNamespace (
764+ input_ids = [],
765+ draft_tokens = [],
766+ draft_lens = [],
767+ position_ids = list (range (sum (lengths ))),
768+ previous_batch_indices = [0 , 1 ],
769+ previous_pos_indices = [0 ] * lengths [0 ] + [1 ] * lengths [1 ],
770+ extend_requests = [object (), object ()],
771+ extend_dummy_requests = [],
772+ generation_requests = [],
773+ unwritten_input_id_rows = [],
774+ ),
775+ previous_batch_tokens_per_request = lengths if trims else None ,
776+ previous_batch_token_start = 0 ,
777+ )
778+ decoder ._copy_token_ids_to_device (
779+ host ,
780+ SimpleNamespace (is_attention_dp_phase_idle = False , num_context_requests = 0 ),
781+ torch .arange (8 , dtype = torch .int32 ).reshape (4 , 2 , 1 ),
782+ torch .tensor ([1 , 2 ], dtype = torch .int32 ),
783+ torch .arange (6 , dtype = torch .int32 ).reshape (2 , 3 ),
784+ 3 ,
785+ attn_metadata = SimpleNamespace (is_cuda_graph = True ),
786+ enable_spec_decode = True ,
787+ )
788+ expected_tail = 0 if trims else - 99
789+ assert decoder ._buffers .previous_pos_id_offsets_cuda [sum (lengths ) :].tolist () == [
790+ expected_tail
791+ ] * (16 - sum (lengths ))
792+ assert decoder ._buffers .previous_kv_lens_offsets_cuda [2 :].tolist () == [expected_tail ] * 14
793+
794+
795+ @pytest .mark .parametrize ("release_owners" , [False , True ])
796+ def test_graph_release_preserves_nccl_window_owner_choice (release_owners : bool ) -> None :
797+ calls = []
798+ graphs = object .__new__ (DecoderCudaGraphs )
799+ graphs .cuda_graph_runner = SimpleNamespace (
800+ clear = lambda ** kwargs : calls .append (("native" , kwargs ))
801+ )
802+ graphs .breakable_cuda_graph_runner = SimpleNamespace (
803+ clear = lambda ** kwargs : calls .append (("breakable" , kwargs ))
804+ )
805+
806+ graphs .release (release_nccl_window_owners = release_owners )
807+
808+ assert calls == [
809+ ("native" , {"release_nccl_window_owners" : release_owners }),
810+ ("breakable" , {"release_nccl_window_owners" : release_owners }),
811+ ]
0 commit comments