2929)
3030
3131import numpy as np
32- from pydantic import Field , computed_field , field_serializer , field_validator
32+ from pydantic import (
33+ Field ,
34+ NonNegativeFloat ,
35+ PositiveFloat ,
36+ PositiveInt ,
37+ computed_field ,
38+ field_serializer ,
39+ field_validator ,
40+ )
3341
42+ from guidellm import settings
3443from guidellm .scheduler import (
3544 AsyncConstantStrategy ,
3645 AsyncPoissonStrategy ,
@@ -86,7 +95,7 @@ def __pydantic_schema_base_type__(cls) -> type[Profile]:
8695 def create (
8796 cls ,
8897 rate_type : str ,
89- rate : float | int | list [float | int ] | None ,
98+ rate : list [float ] | None ,
9099 random_seed : int = 42 ,
91100 ** kwargs : Any ,
92101 ) -> Profile :
@@ -112,7 +121,7 @@ def create(
112121 def resolve_args (
113122 cls ,
114123 rate_type : str ,
115- rate : float | int | list [float , int ] | None ,
124+ rate : list [float ] | None ,
116125 random_seed : int ,
117126 ** kwargs : Any ,
118127 ) -> dict [str , Any ]:
@@ -265,7 +274,7 @@ class SynchronousProfile(Profile):
265274 def resolve_args (
266275 cls ,
267276 rate_type : str ,
268- rate : float | int | list [float , int ] | None ,
277+ rate : list [float ] | None ,
269278 random_seed : int ,
270279 ** kwargs : Any ,
271280 ) -> dict [str , Any ]:
@@ -316,24 +325,22 @@ class ConcurrentProfile(Profile):
316325 """Fixed-concurrency strategy execution profile with configurable stream counts."""
317326
318327 type_ : Literal ["concurrent" ] = "concurrent" # type: ignore[assignment]
319- streams : int | list [int ] = Field (
328+ streams : list [PositiveInt ] = Field (
320329 description = "Number of concurrent streams for request scheduling" ,
321- gt = 0 ,
322330 )
323- startup_duration : float = Field (
331+ startup_duration : NonNegativeFloat = Field (
324332 default = 0.0 ,
325333 description = (
326334 "Duration in seconds for distributing startup requests "
327335 "before completion-based timing"
328336 ),
329- ge = 0 ,
330337 )
331338
332339 @classmethod
333340 def resolve_args (
334341 cls ,
335342 rate_type : str ,
336- rate : float | int | list [float , int ] | None ,
343+ rate : list [float ] | None ,
337344 random_seed : int ,
338345 ** kwargs : Any ,
339346 ) -> dict [str , Any ]:
@@ -348,14 +355,13 @@ def resolve_args(
348355 :raises ValueError: If rate is None.
349356 """
350357 _ = (rate_type , random_seed ) # unused
351- kwargs ["streams" ] = rate
358+ kwargs ["streams" ] = [ int ( r ) for r in rate ] if rate else None
352359 return kwargs
353360
354361 @property
355362 def strategy_types (self ) -> list [StrategyType ]:
356363 """Get concurrent strategy types for each configured stream count."""
357- num_strategies = len (self .streams ) if isinstance (self .streams , list ) else 1
358- return [self .type_ ] * num_strategies
364+ return [self .type_ ] * len (self .streams )
359365
360366 def next_strategy (
361367 self ,
@@ -370,13 +376,12 @@ def next_strategy(
370376 :return: ConcurrentStrategy with next stream count, or None if complete.
371377 """
372378 _ = (prev_strategy , prev_benchmark ) # unused
373- streams = self .streams if isinstance (self .streams , list ) else [self .streams ]
374379
375- if len (self .completed_strategies ) >= len (streams ):
380+ if len (self .completed_strategies ) >= len (self . streams ):
376381 return None
377382
378383 return ConcurrentStrategy (
379- streams = streams [len (self .completed_strategies )],
384+ streams = self . streams [len (self .completed_strategies )],
380385 startup_duration = self .startup_duration ,
381386 )
382387
@@ -388,25 +393,22 @@ class ThroughputProfile(Profile):
388393 """
389394
390395 type_ : Literal ["throughput" ] = "throughput" # type: ignore[assignment]
391- max_concurrency : int | None = Field (
396+ max_concurrency : PositiveInt | None = Field (
392397 default = None ,
393398 description = "Maximum number of concurrent requests to schedule" ,
394- gt = 0 ,
395399 )
396- startup_duration : float = Field (
397- default = 0.0 ,
400+ startup_duration : NonNegativeFloat = Field (
398401 description = (
399402 "Duration in seconds for distributing startup requests "
400403 "before full throughput scheduling"
401404 ),
402- ge = 0 ,
403405 )
404406
405407 @classmethod
406408 def resolve_args (
407409 cls ,
408410 rate_type : str ,
409- rate : float | int | list [float , int ] | None ,
411+ rate : list [float ] | None ,
410412 random_seed : int ,
411413 ** kwargs : Any ,
412414 ) -> dict [str , Any ]:
@@ -422,8 +424,8 @@ def resolve_args(
422424 _ = (rate_type , random_seed ) # unused
423425 # Remap rate to max_concurrency, strip out random_seed
424426 kwargs .pop ("random_seed" , None )
425- if rate is not None :
426- kwargs ["max_concurrency" ] = rate
427+ if rate is not None and len ( rate ) > 0 :
428+ kwargs ["max_concurrency" ] = rate [ 0 ]
427429 return kwargs
428430
429431 @property
@@ -463,22 +465,19 @@ class AsyncProfile(Profile):
463465 strategy_type : Literal ["constant" , "poisson" ] = Field (
464466 description = "Type of asynchronous strategy pattern to use" ,
465467 )
466- rate : float | list [float ] = Field (
468+ rate : list [PositiveFloat ] = Field (
467469 description = "Request scheduling rate in requests per second" ,
468- gt = 0 ,
469470 )
470- startup_duration : float = Field (
471+ startup_duration : NonNegativeFloat = Field (
471472 default = 0.0 ,
472473 description = (
473474 "Duration in seconds for distributing startup requests "
474475 "to converge quickly to desired rate"
475476 ),
476- ge = 0 ,
477477 )
478- max_concurrency : int | None = Field (
478+ max_concurrency : PositiveInt | None = Field (
479479 default = None ,
480480 description = "Maximum number of concurrent requests to schedule" ,
481- gt = 0 ,
482481 )
483482 random_seed : int = Field (
484483 default = 42 ,
@@ -489,7 +488,7 @@ class AsyncProfile(Profile):
489488 def resolve_args (
490489 cls ,
491490 rate_type : str ,
492- rate : float | int | list [float , int ] | None ,
491+ rate : list [float ] | None ,
493492 random_seed : int ,
494493 ** kwargs : Any ,
495494 ) -> dict [str , Any ]:
@@ -523,7 +522,7 @@ def resolve_args(
523522 @property
524523 def strategy_types (self ) -> list [StrategyType ]:
525524 """Get async strategy types for each configured rate."""
526- num_strategies = len (self .rate ) if isinstance ( self . rate , list ) else 1
525+ num_strategies = len (self .rate )
527526 return [self .strategy_type ] * num_strategies
528527
529528 def next_strategy (
@@ -541,12 +540,11 @@ def next_strategy(
541540 :raises ValueError: If strategy_type is neither 'constant' nor 'poisson'.
542541 """
543542 _ = (prev_strategy , prev_benchmark ) # unused
544- rate = self .rate if isinstance (self .rate , list ) else [self .rate ]
545543
546- if len (self .completed_strategies ) >= len (rate ):
544+ if len (self .completed_strategies ) >= len (self . rate ):
547545 return None
548546
549- current_rate = rate [len (self .completed_strategies )]
547+ current_rate = self . rate [len (self .completed_strategies )]
550548
551549 if self .strategy_type == "constant" :
552550 return AsyncConstantStrategy (
@@ -577,18 +575,16 @@ class SweepProfile(Profile):
577575 ge = 2 ,
578576 )
579577 strategy_type : Literal ["constant" , "poisson" ] = "constant"
580- startup_duration : float = Field (
578+ startup_duration : NonNegativeFloat = Field (
581579 default = 0.0 ,
582580 description = (
583581 "Duration in seconds for distributing startup requests "
584582 "to converge quickly to desired rate"
585583 ),
586- ge = 0 ,
587584 )
588- max_concurrency : int | None = Field (
585+ max_concurrency : PositiveInt | None = Field (
589586 default = None ,
590587 description = "Maximum number of concurrent requests to schedule" ,
591- gt = 0 ,
592588 )
593589 random_seed : int = Field (
594590 default = 42 ,
@@ -615,7 +611,7 @@ class SweepProfile(Profile):
615611 def resolve_args (
616612 cls ,
617613 rate_type : str ,
618- rate : float | int | list [float , int ] | None ,
614+ rate : list [float ] | None ,
619615 random_seed : int ,
620616 ** kwargs : Any ,
621617 ) -> dict [str , Any ]:
@@ -628,7 +624,8 @@ def resolve_args(
628624 :param kwargs: Additional arguments to pass through.
629625 :return: Dictionary of resolved arguments.
630626 """
631- kwargs ["sweep_size" ] = kwargs .get ("sweep_size" , rate )
627+ sweep_size_from_rate = int (rate [0 ]) if rate else settings .default_sweep_number
628+ kwargs ["sweep_size" ] = kwargs .get ("sweep_size" , sweep_size_from_rate )
632629 kwargs ["random_seed" ] = random_seed
633630 if rate_type in ["constant" , "poisson" ]:
634631 kwargs ["strategy_type" ] = rate_type
0 commit comments