Skip to content

Commit 2adff39

Browse files
authored
Initialize rand once, added seed to configuration (#79)
* Initialize rand once, added seed to configuration Signed-off-by: Ira <IRAR@il.ibm.com> * Added a test, updated README Signed-off-by: Ira <IRAR@il.ibm.com> --------- Signed-off-by: Ira <IRAR@il.ibm.com>
1 parent 3e63a0d commit 2adff39

File tree

8 files changed

+156
-11
lines changed

8 files changed

+156
-11
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ For more details see the <a href="https://docs.vllm.ai/en/stable/getting_started
9898
- `random`: returns a sentence chosen at random from a set of pre-defined sentences
9999
- `time-to-first-token`: the time to the first token (in milliseconds), optional, by default zero
100100
- `inter-token-latency`: the time to 'generate' each additional token (in milliseconds), optional, by default zero
101+
- `seed`: random seed for operations (if not set, current Unix time in nanoseconds is used)
101102

102103
In addition, as we are using klog, the following parameters are available:
103104
- `add_dir_header`: if true, adds the file directory to the header of the log messages

manifests/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ lora-modules:
1212
mode: "random"
1313
time-to-first-token: 2
1414
inter-token-latency: 1
15+
seed: 100100100

pkg/llm-d-inference-sim/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"os"
2424
"strings"
25+
"time"
2526

2627
"gopkg.in/yaml.v3"
2728
)
@@ -51,6 +52,8 @@ type configuration struct {
5152
InterTokenLatency int `yaml:"inter-token-latency"`
5253
// Mode defines the simulator response generation mode, valid values: echo, random
5354
Mode string `yaml:"mode"`
55+
// Seed defines random seed for operations
56+
Seed int64 `yaml:"seed"`
5457
}
5558

5659
type loraModule struct {
@@ -98,6 +101,7 @@ func newConfig() *configuration {
98101
MaxLoras: 1,
99102
MaxNumSeqs: 5,
100103
Mode: modeRandom,
104+
Seed: time.Now().UnixNano(),
101105
}
102106
}
103107

pkg/llm-d-inference-sim/config_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,15 @@ type testCase struct {
5050
var _ = Describe("Simulator configuration", func() {
5151
tests := make([]testCase, 0)
5252

53-
// Simple config with only model name set
53+
// Simple config with a few parameters
5454
c := newConfig()
5555
c.Model = model
5656
c.ServedModelNames = []string{c.Model}
5757
c.MaxCPULoras = 1
58+
c.Seed = 100
5859
test := testCase{
5960
name: "simple",
60-
args: []string{"cmd", "--model", model, "--mode", modeRandom},
61+
args: []string{"cmd", "--model", model, "--mode", modeRandom, "--seed", "100"},
6162
expectedConfig: c,
6263
}
6364
tests = append(tests, test)
@@ -73,6 +74,7 @@ var _ = Describe("Simulator configuration", func() {
7374
c.TimeToFirstToken = 2
7475
c.InterTokenLatency = 1
7576
c.LoraModules = []loraModule{{Name: "lora1", Path: "/path/to/lora1"}, {Name: "lora2", Path: "/path/to/lora2"}}
77+
c.Seed = 100100100
7678
test = testCase{
7779
name: "config file",
7880
args: []string{"cmd", "--config", "../../manifests/config.yaml"},
@@ -94,6 +96,7 @@ var _ = Describe("Simulator configuration", func() {
9496
c.MaxNumSeqs = 5
9597
c.TimeToFirstToken = 2
9698
c.InterTokenLatency = 1
99+
c.Seed = 100
97100
c.LoraModules = []loraModule{{Name: "lora3", Path: "/path/to/lora3"}, {Name: "lora4", Path: "/path/to/lora4"}}
98101
c.LoraModulesString = []string{
99102
"{\"name\":\"lora3\",\"path\":\"/path/to/lora3\"}",
@@ -102,7 +105,7 @@ var _ = Describe("Simulator configuration", func() {
102105
test = testCase{
103106
name: "config file with command line args",
104107
args: []string{"cmd", "--model", model, "--config", "../../manifests/config.yaml", "--port", "8002",
105-
"--served-model-name", "alias1", "alias2",
108+
"--served-model-name", "alias1", "alias2", "--seed", "100",
106109
"--lora-modules", "{\"name\":\"lora3\",\"path\":\"/path/to/lora3\"}", "{\"name\":\"lora4\",\"path\":\"/path/to/lora4\"}",
107110
},
108111
expectedConfig: c,
@@ -119,6 +122,7 @@ var _ = Describe("Simulator configuration", func() {
119122
c.MaxNumSeqs = 5
120123
c.TimeToFirstToken = 2
121124
c.InterTokenLatency = 1
125+
c.Seed = 100100100
122126
c.LoraModules = []loraModule{{Name: "lora3", Path: "/path/to/lora3"}}
123127
c.LoraModulesString = []string{
124128
"{\"name\":\"lora3\",\"path\":\"/path/to/lora3\"}",
@@ -143,6 +147,7 @@ var _ = Describe("Simulator configuration", func() {
143147
c.MaxNumSeqs = 5
144148
c.TimeToFirstToken = 2
145149
c.InterTokenLatency = 1
150+
c.Seed = 100100100
146151
c.LoraModules = []loraModule{{Name: "lora3", Path: "/path/to/lora3"}}
147152
c.LoraModulesString = []string{
148153
"{\"name\":\"lora3\",\"path\":\"/path/to/lora3\"}",

pkg/llm-d-inference-sim/seed_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
Copyright 2025 The llm-d-inference-sim Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package llmdinferencesim
18+
19+
import (
20+
"context"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
"github.com/openai/openai-go"
25+
"github.com/openai/openai-go/option"
26+
)
27+
28+
var _ = Describe("Simulator with seed", func() {
29+
firstText := ""
30+
DescribeTable("text completions with the same seed",
31+
// use a function so that httpClient is captured when running
32+
func() {
33+
ctx := context.TODO()
34+
client, err := startServerWithArgs(ctx, modeRandom,
35+
[]string{"cmd", "--model", model, "--mode", modeRandom, "--seed", "100"})
36+
Expect(err).NotTo(HaveOccurred())
37+
38+
openaiclient := openai.NewClient(
39+
option.WithBaseURL(baseURL),
40+
option.WithHTTPClient(client))
41+
params := openai.CompletionNewParams{
42+
Prompt: openai.CompletionNewParamsPromptUnion{
43+
OfString: openai.String(userMessage),
44+
},
45+
Model: openai.CompletionNewParamsModel(model),
46+
}
47+
48+
resp, err := openaiclient.Completions.New(ctx, params)
49+
Expect(err).NotTo(HaveOccurred())
50+
Expect(resp.Choices).ShouldNot(BeEmpty())
51+
Expect(string(resp.Object)).To(Equal(textCompletionObject))
52+
53+
text := resp.Choices[0].Text
54+
Expect(text).ShouldNot(BeEmpty())
55+
if firstText == "" {
56+
firstText = text
57+
} else {
58+
Expect(text).Should(Equal(firstText))
59+
}
60+
},
61+
Entry("first time text completion with seed"),
62+
Entry("second time text completion with seed"),
63+
Entry("third time text completion with seed"),
64+
Entry("fourth time text completion with seed"),
65+
Entry("fifth time text completion with seed"),
66+
Entry("sixth time text completion with seed"),
67+
Entry("seventh time text completion with seed"),
68+
Entry("eighth time text completion with seed"),
69+
)
70+
71+
texts := make([]string, 0)
72+
DescribeTable("text completions with different seeds",
73+
func(lastTest bool) {
74+
ctx := context.TODO()
75+
client, err := startServer(ctx, modeRandom)
76+
Expect(err).NotTo(HaveOccurred())
77+
78+
openaiclient := openai.NewClient(
79+
option.WithBaseURL(baseURL),
80+
option.WithHTTPClient(client))
81+
params := openai.CompletionNewParams{
82+
Prompt: openai.CompletionNewParamsPromptUnion{
83+
OfString: openai.String(userMessage),
84+
},
85+
Model: openai.CompletionNewParamsModel(model),
86+
}
87+
88+
resp, err := openaiclient.Completions.New(ctx, params)
89+
Expect(err).NotTo(HaveOccurred())
90+
Expect(resp.Choices).ShouldNot(BeEmpty())
91+
Expect(string(resp.Object)).To(Equal(textCompletionObject))
92+
93+
text := resp.Choices[0].Text
94+
Expect(text).ShouldNot(BeEmpty())
95+
texts = append(texts, text)
96+
if lastTest {
97+
Expect(hasAtLeastTwoDifferentTexts(texts)).To(BeTrue())
98+
}
99+
},
100+
Entry("first time text completion without seed", false),
101+
Entry("second time text completion without seed", false),
102+
Entry("third time text completion without seed", false),
103+
Entry("fourth time text completion without seed", false),
104+
Entry("fifth time text completion without seed", false),
105+
Entry("sixth time text completion without seed", false),
106+
Entry("seventh time text completion without seed", false),
107+
Entry("eighth time text completion without seed", true),
108+
)
109+
})
110+
111+
func hasAtLeastTwoDifferentTexts(texts []string) bool {
112+
unique := make(map[string]struct{})
113+
for _, s := range texts {
114+
unique[s] = struct{}{}
115+
if len(unique) > 1 {
116+
return true
117+
}
118+
}
119+
return false
120+
}

pkg/llm-d-inference-sim/simulator.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ func (s *VllmSimulator) parseCommandParamsAndLoadConfig() error {
154154
f.StringVar(&config.Mode, "mode", config.Mode, "Simulator mode, echo - returns the same text that was sent in the request, for chat completion returns the last message, random - returns random sentence from a bank of pre-defined sentences")
155155
f.IntVar(&config.InterTokenLatency, "inter-token-latency", config.InterTokenLatency, "Time to generate one token (in milliseconds)")
156156
f.IntVar(&config.TimeToFirstToken, "time-to-first-token", config.TimeToFirstToken, "Time to first token (in milliseconds)")
157+
f.Int64Var(&config.Seed, "seed", config.Seed, "Random seed for operations (if not set, current Unix time in nanoseconds is used)")
157158

158159
// These values were manually parsed above in getParamValueFromArgs, we leave this in order to get these flags in --help
159160
var servedModelNameStrings multiString
@@ -192,6 +193,8 @@ func (s *VllmSimulator) parseCommandParamsAndLoadConfig() error {
192193
s.loraAdaptors.Store(lora, "")
193194
}
194195

196+
initRandom(s.config.Seed)
197+
195198
// just to suppress not used lint error for now
196199
_ = &s.waitingLoras
197200
return nil

pkg/llm-d-inference-sim/simulator_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,20 @@ const baseURL = "http://localhost/v1"
4040
const userMessage = "This is a test."
4141

4242
func startServer(ctx context.Context, mode string) (*http.Client, error) {
43+
return startServerWithArgs(ctx, mode, nil)
44+
}
45+
46+
func startServerWithArgs(ctx context.Context, mode string, args []string) (*http.Client, error) {
4347
oldArgs := os.Args
4448
defer func() {
4549
os.Args = oldArgs
4650
}()
47-
os.Args = []string{"cmd", "--model", model, "--mode", mode}
51+
52+
if args != nil {
53+
os.Args = args
54+
} else {
55+
os.Args = []string{"cmd", "--model", model, "--mode", mode}
56+
}
4857
logger := klog.Background()
4958

5059
s, err := New(logger)

pkg/llm-d-inference-sim/utils.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"math/rand"
2222
"regexp"
2323
"strings"
24-
"time"
2524
)
2625

2726
// list of responses to use in random mode for comepltion requests
@@ -111,11 +110,16 @@ func randomNumericString(length int) string {
111110
return string(result)
112111
}
113112

113+
var randomGenerator *rand.Rand
114+
115+
func initRandom(seed int64) {
116+
src := rand.NewSource(seed)
117+
randomGenerator = rand.New(src)
118+
}
119+
114120
// Returns an integer between min and max (included)
115121
func randomInt(min int, max int) int {
116-
src := rand.NewSource(time.Now().UnixNano())
117-
r := rand.New(src)
118-
return r.Intn(max-min+1) + min
122+
return randomGenerator.Intn(max-min+1) + min
119123
}
120124

121125
// Returns true or false randomly
@@ -125,9 +129,7 @@ func flipCoin() bool {
125129

126130
// Returns a random float64 in the range [min, max)
127131
func randomFloat(min float64, max float64) float64 {
128-
src := rand.NewSource(time.Now().UnixNano())
129-
r := rand.New(src)
130-
return r.Float64()*(max-min) + min
132+
return randomGenerator.Float64()*(max-min) + min
131133
}
132134

133135
// Regular expression for the response tokenization

0 commit comments

Comments
 (0)