@@ -3825,6 +3825,7 @@ def test_set_inputs_first_pass_pcp_dcp_mixed(self):
38253825 self .runner .input_batch .req_ids = req_ids
38263826 # maybe not reasonable just to run test
38273827 self .runner .logits_indices = torch .arange (12 , dtype = torch .int32 , device = self .device )
3828+ self .runner .pcp_manager .pcp_use_hybrid_attn = False
38283829
38293830 proposer , vllm_config = self ._create_proposer (
38303831 method = "eagle" ,
@@ -4205,3 +4206,179 @@ def test_set_inputs_first_pass_draft_model(self):
42054206
42064207 for attrition in attrs_from_cad :
42074208 assert_attr_equal (attrition , expected_cad , out_cad )
4209+
4210+
4211+ def _build_split_pcp_input_hybrid_inputs (req_scheduled_tokens : dict [str , int ], hidden_size : int ):
4212+ """Build input_ids and target_hidden_states where row i is filled with i.
4213+
4214+ Using the row index as the value makes it easy to assert which original
4215+ tokens survive the per-rank slice and which slots are padding.
4216+ """
4217+ total_tokens = sum (req_scheduled_tokens .values ())
4218+ input_ids = torch .arange (total_tokens , dtype = torch .int32 )
4219+ target_hidden_states = torch .arange (total_tokens , dtype = torch .float32 ).unsqueeze (- 1 ).repeat (1 , hidden_size )
4220+ return input_ids , target_hidden_states
4221+
4222+
4223+ # yapf: disable
4224+ @pytest .mark .parametrize (
4225+ "pcp_size, pcp_rank, req_scheduled_tokens, hidden_size,"
4226+ " expected_num_tokens, expected_input_ids, expected_hidden_first_col,"
4227+ " expected_seq_lens, expected_cu_num_tokens, expected_max_query_len" ,
4228+ [
4229+ # Case 1: single req, perfectly aligned to 2*pcp_size.
4230+ # ori=8, pcp=2 -> padded=8, pcp_tokens=4
4231+ # rank 0: [0,1,2,3], rank 1: [4,5,6,7]
4232+ (
4233+ 2 , 0 , {"0" : 8 }, 4 ,
4234+ 4 , [0 , 1 , 2 , 3 ], [0.0 , 1.0 , 2.0 , 3.0 ],
4235+ [4 ], [0 , 4 ], 4 ,
4236+ ),
4237+ (
4238+ 2 , 1 , {"0" : 8 }, 4 ,
4239+ 4 , [4 , 5 , 6 , 7 ], [4.0 , 5.0 , 6.0 , 7.0 ],
4240+ [4 ], [0 , 4 ], 4 ,
4241+ ),
4242+ # Case 2: single req, needs padding.
4243+ # ori=7, pcp=2 -> padded=8, pcp_tokens=4, num_pads=1
4244+ # rank 0: [0,1,2,3] (all valid)
4245+ # rank 1: [4,5,6,PAD] -> input_ids=[4,5,6,0], hidden_first_col=[4,5,6,0]
4246+ (
4247+ 2 , 0 , {"0" : 7 }, 4 ,
4248+ 4 , [0 , 1 , 2 , 3 ], [0.0 , 1.0 , 2.0 , 3.0 ],
4249+ [4 ], [0 , 4 ], 4 ,
4250+ ),
4251+ (
4252+ 2 , 1 , {"0" : 7 }, 4 ,
4253+ 4 , [4 , 5 , 6 , 0 ], [4.0 , 5.0 , 6.0 , 0.0 ],
4254+ [4 ], [0 , 4 ], 4 ,
4255+ ),
4256+ # Case 3: multiple reqs with different padding needs.
4257+ # req 0: ori=4 -> padded=4, pcp_tokens=2
4258+ # req 1: ori=6 -> padded=8, pcp_tokens=4, num_pads=2
4259+ # rank 0: [0,1] + [4,5,6,7]
4260+ # rank 1: [2,3] + [8,9,PAD,PAD]
4261+ (
4262+ 2 , 0 , {"0" : 4 , "1" : 6 }, 2 ,
4263+ 6 , [0 , 1 , 4 , 5 , 6 , 7 ], [0.0 , 1.0 , 4.0 , 5.0 , 6.0 , 7.0 ],
4264+ [2 , 4 ], [0 , 2 , 6 ], 4 ,
4265+ ),
4266+ (
4267+ 2 , 1 , {"0" : 4 , "1" : 6 }, 2 ,
4268+ 6 , [2 , 3 , 8 , 9 , 0 , 0 ], [2.0 , 3.0 , 8.0 , 9.0 , 0.0 , 0.0 ],
4269+ [2 , 4 ], [0 , 2 , 6 ], 4 ,
4270+ ),
4271+ # Case 4: pcp_size=4
4272+ # ori=9, pcp=4 -> padded=16, pcp_tokens=4, num_pads=7
4273+ # rank 0: tokens [0,1,2,3]
4274+ # rank 1: tokens [4,5,6,7]
4275+ # rank 2: tokens [8,PAD,PAD,PAD]
4276+ # rank 3: tokens [PAD,PAD,PAD,PAD]
4277+ (
4278+ 4 , 0 , {"0" : 9 }, 2 ,
4279+ 4 , [0 , 1 , 2 , 3 ], [0.0 , 1.0 , 2.0 , 3.0 ],
4280+ [4 ], [0 , 4 ], 4 ,
4281+ ),
4282+ (
4283+ 4 , 1 , {"0" : 9 }, 2 ,
4284+ 4 , [4 , 5 , 6 , 7 ], [4.0 , 5.0 , 6.0 , 7.0 ],
4285+ [4 ], [0 , 4 ], 4 ,
4286+ ),
4287+ (
4288+ 4 , 2 , {"0" : 9 }, 2 ,
4289+ 4 , [8 , 0 , 0 , 0 ], [8.0 , 0.0 , 0.0 , 0.0 ],
4290+ [4 ], [0 , 4 ], 4 ,
4291+ ),
4292+ (
4293+ 4 , 3 , {"0" : 9 }, 2 ,
4294+ 4 , [0 , 0 , 0 , 0 ], [0.0 , 0.0 , 0.0 , 0.0 ],
4295+ [4 ], [0 , 4 ], 4 ,
4296+ ),
4297+ # Case 5: minimal request - single token.
4298+ # ori=1, pcp=2 -> padded=4, pcp_tokens=2, num_pads=3
4299+ # rank 0: [0, PAD]
4300+ # rank 1: [PAD, PAD]
4301+ (
4302+ 2 , 0 , {"0" : 1 }, 2 ,
4303+ 2 , [0 , 0 ], [0.0 , 0.0 ],
4304+ [2 ], [0 , 2 ], 2 ,
4305+ ),
4306+ (
4307+ 2 , 1 , {"0" : 1 }, 2 ,
4308+ 2 , [0 , 0 ], [0.0 , 0.0 ],
4309+ [2 ], [0 , 2 ], 2 ,
4310+ ),
4311+ ],
4312+ )
4313+ # yapf: enable
4314+ def test_split_pcp_input_hybrid (
4315+ pcp_size ,
4316+ pcp_rank ,
4317+ req_scheduled_tokens ,
4318+ hidden_size ,
4319+ expected_num_tokens ,
4320+ expected_input_ids ,
4321+ expected_hidden_first_col ,
4322+ expected_seq_lens ,
4323+ expected_cu_num_tokens ,
4324+ expected_max_query_len ,
4325+ ):
4326+ input_ids , target_hidden_states = _build_split_pcp_input_hybrid_inputs (
4327+ req_scheduled_tokens , hidden_size
4328+ )
4329+
4330+ # _split_pcp_input_hybrid only reads self.pcp_size and self.pcp_rank,
4331+ # so a MagicMock with just those attributes is enough to drive the
4332+ # unbound method without instantiating the full proposer.
4333+ mock_self = MagicMock ()
4334+ mock_self .pcp_size = pcp_size
4335+ mock_self .pcp_rank = pcp_rank
4336+
4337+ (
4338+ num_tokens ,
4339+ out_input_ids ,
4340+ out_hidden_states ,
4341+ max_query_len ,
4342+ seq_lens ,
4343+ cu_num_tokens ,
4344+ ) = llm_base_proposer .AscendSpecDecodeBaseProposer ._split_pcp_input_hybrid (
4345+ mock_self , req_scheduled_tokens , input_ids , target_hidden_states
4346+ )
4347+
4348+ assert num_tokens == expected_num_tokens
4349+ assert max_query_len == expected_max_query_len
4350+ assert torch .equal (
4351+ out_input_ids , torch .tensor (expected_input_ids , dtype = torch .int32 )
4352+ )
4353+ assert torch .equal (seq_lens , torch .tensor (expected_seq_lens , dtype = torch .int32 ))
4354+ assert torch .equal (
4355+ cu_num_tokens , torch .tensor (expected_cu_num_tokens , dtype = torch .int64 )
4356+ )
4357+
4358+ # hidden_states shape: [num_tokens, hidden_size]
4359+ assert out_hidden_states .shape == (expected_num_tokens , hidden_size )
4360+ assert torch .equal (
4361+ out_hidden_states [:, 0 ],
4362+ torch .tensor (expected_hidden_first_col , dtype = torch .float32 ),
4363+ )
4364+
4365+
4366+ def test_split_pcp_input_hybrid_preserves_hidden_size ():
4367+ """Hidden states must come back with the same hidden dim as the input."""
4368+ hidden_size = 7
4369+ req_scheduled_tokens = {"0" : 6 }
4370+ input_ids , target_hidden_states = _build_split_pcp_input_hybrid_inputs (
4371+ req_scheduled_tokens , hidden_size
4372+ )
4373+
4374+ mock_self = MagicMock ()
4375+ mock_self .pcp_size = 2
4376+ mock_self .pcp_rank = 0
4377+
4378+ _ , _ , out_hidden_states , _ , _ , _ = (
4379+ llm_base_proposer .AscendSpecDecodeBaseProposer ._split_pcp_input_hybrid (
4380+ mock_self , req_scheduled_tokens , input_ids , target_hidden_states
4381+ )
4382+ )
4383+
4384+ assert out_hidden_states .shape [1 ] == hidden_size
0 commit comments