Skip to content

seed is ignored by the sampler, and a negative seed crashes the server #219

Closed

Description

@HamedShams

Found this while reading how the handlers set up sampling, then confirmed it on an M1 Max 64 GB (main @ 1b6befd, mlx-swift-lm @ 4f1fdaa, macOS 27, Xcode 27 / Swift 6.4, mlx-community/Qwen3-0.6B-4bit).

1. seed has no effect on sampling

Both handlers handle a request seed by seeding MLX's global generator:

if let seed = chatReq.seed {
MLXRandom.seed(UInt64(seed))
}

but the samplers don't draw from the global generator. They are seeded through GenerateParameters.seed, and when that is nil each TopPSampler / CategoricalSampler creates its own entropy-seeded RandomState:

https://github.com/SharpAI/mlx-swift-lm/blob/4f1fdaabeb490d3e97b0ff71fc0132bca4078fa2/Libraries/MLXLMCommon/Evaluate.swift#L423-L425

Neither GenerateParameters(...) call in Server.swift passes seed:, so the field is accepted and silently ignored.

Repro: the same request three times with temperature: 0.9, seed: 42:

"There's a very important thing to remember: if"
"The animal is **a bird**."
"The animal is a bird."

temperature: 0 is reproducible as expected, so it's the sampling path specifically, not prefill.

2. A negative seed crashes the server

UInt64(seed) traps on a negative Int:

curl -s localhost:5413/v1/chat/completions -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"hi"}],"max_tokens":3,"seed":-1}'
# curl: (52) Empty reply from server — and the process is gone

From the crash report:

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
0  SwiftLM  Swift runtime failure: Negative value is not representable
1  SwiftLM  handleChatCompletion(request:bodyData:...) + 3660  (Server.swift:2387)

handleTextCompletion has the same conversion at L3561. OpenAI's seed is a plain integer, nothing stops a client from sending a negative one, and one such request takes down every other client's session with it.

Suggested fix

  • Pass the seed into GenerateParameters(seed:) so it reaches the sampler. The global MLXRandom.seed call can stay for the paths that call MLXRandom.categorical directly.
  • Convert with UInt64(bitPattern: Int64(seed)) instead of UInt64(seed).

Happy to send a PR with a contract test for both halves (same seed twice → identical output; negative seed → 200 and the server still answers /health).

Activity

  1. solderzzc commented on Oct 9, 2026

    @solderzzc
    Member

    Thanks for the detailed report. I checked it against main and the code matches your description:

    • Server.swift only calls MLXRandom.seed(...) (L2386-2388 and L3560-3562), while neither GenerateParameters(...) call (L2371, L3547) passes seed:. GenerateParameters.seed is what seeds the TopPSampler / CategoricalSampler RNG (mlx-swift-lm Evaluate.swift), so the request field is accepted but has no effect on those samplers.
    • UInt64(seed) traps for a negative Int, so a single request can take the whole server down.

    I have not run your curl repro myself, so the reproducibility and crash behaviour is confirmed from the code only.

    Your suggested fix looks right to us: pass the seed through GenerateParameters(seed:), keep the global MLXRandom.seed for paths that call MLXRandom.categorical directly, and convert with UInt64(bitPattern: Int64(seed)). A PR with a contract test for both halves (same seed twice gives identical output; negative seed gives 200 and /health still answers) would be very welcome.

  2. HamedShams commented on Oct 9, 2026

    @HamedShams
    ContributorAuthor

    Thanks for checking it against the code. The curl repros were real runs on an M1 Max, and the crash report in the issue is from one of them, so you don't need to re-run it on your side.

    The PR is already up: #220. It does what you describe: GenerateParameters.seed set in both handlers, the global MLXRandom.seed kept for the paths that call MLXRandom.categorical directly, and UInt64(bitPattern: Int64(seed)) for the conversion. The contract script got both assertions (same seed twice → identical message; seed: -1 → 200 and /health still answers), plus a unit test for the mapping. CI is green across the matrix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions