Skip to content

ci: production-model matrix — build-once fan-out, gemma-4 + gpt-oss, concurrency stress suite#497

Open
Gajesh2007 wants to merge 2 commits into
masterfrom
ci/prod-model-matrix
Open

ci: production-model matrix — build-once fan-out, gemma-4 + gpt-oss, concurrency stress suite#497
Gajesh2007 wants to merge 2 commits into
masterfrom
ci/prod-model-matrix

Conversation

@Gajesh2007

@Gajesh2007 Gajesh2007 commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

Integration CI now tests the models the fleet servesgemma-4-26b-qat-4bit (15.6 GB) and gpt-oss-20b (12.1 GB), solo per job, in parallel — instead of the retired Qwen3.5-0.8B. Build-once/fan-out architecture, aggressive caching, and a new concurrency stress suite that turns the broadcast_shapes engine-crash class from a random flake into a deterministic signal.

Before / After

flowchart LR
  subgraph Before
    A1["2 serial jobs<br/>each: full Swift rebuild (~8m)<br/>+ duplicated setup"] --> B1["tests vs Qwen3.5-0.8B only<br/>(retired model, 0.5 GB)"]
    B1 --> C1["~22m wall · no prod-model coverage<br/>no stress · flaky engine crashes"]
  end
  subgraph After
    A2["build once (SwiftPM+metallib cached)<br/>-> binary artifact"] --> B2["7 parallel jobs:<br/>integration x2 models · stress x2<br/>profile · benchmark x2 -> 1 PR comment"]
    B2 --> C2["~12-17m wall · both prod models<br/>R2+SHA-256 download path exercised<br/>deterministic crash detection"]
  end
Loading

Architecture

  • build → SwiftPM cache (own key family, no thrash vs ci.yml) + metallib cached by mlx version → provider-binary artifact
  • integration / stress matrices (fail-fast: false) over both prod models; profile (gemma); benchmark matrix (PR-only) → benchmark-report merges per-model markdown into one PR comment
  • Test jobs checkout without submodules and consume the artifact (DARKBLOOM_PROVIDER_BINARY / MLX_METALLIB_PATH, plus a .build/debug copy for the testbed's cached-binary short-circuit)
  • Models download via the real fleet path: darkbloom models download <id> --coordinator https://api.darkbloom.dev — R2 CDN with per-file + aggregate SHA-256 verification, no config bootstrap needed (verified: catalog is public, loadRuntimeSnapshot falls back to hardware defaults)
  • concurrency: group cancels superseded runs; shared setup lives in a composite action (.github/actions/e2e-setup)

Testbed changes

  • TESTBED_MODEL / TESTBED_MODEL_GB knobs; DefaultModelID() is the single resolution point (default flipped to gpt-oss-20b; all 8 hardcoded Qwen literals routed through it). gemma-3-270m stays only as the multi-model swap-mechanics target.
  • Provider.Alive() / ExitCode() liveness accessors.

New: TestStress_ConcurrentAdmission

3 waves × 8 concurrent streams; 25% of each wave cancelled mid-stream, timed so batch leaves race the next wave's batch joins. Asserts: provider process survives, zero hangs, no non-503 5xx clusters, no truncated non-victim streams, ≥80% completion, accounting integrity. Fail-fast on process death (~24s instead of 200s).

⚠️ Validation note: a local smoke against a dev engine build reproduced the broadcast_shapes fatal deterministically at concurrency ≥2 (Shapes (1,8,22,64) and (1,1,14,64) cannot be broadcast). That was a WIP engine binary — this PR's own stress legs are the arbiter for whether master's engine + prod models are affected. If they come back red, that's the suite working as designed (deterministic evidence of the engine bug on shipped models), and the fix belongs in a follow-up engine PR — we can then decide whether stress blocks merge or runs continue-on-error until the engine lands.

Second first-run validation: 15.6 GB of wired Metal memory in a virtualized M4 Pro (today's CI loads 0.5 GB).

Verification

  • gofmt/go vet clean; e2e + testbed unit tests green; workflow + composite YAML validated; actionlint clean (except pre-existing Blacksmith runner-label false positives)
  • Env-knob unit tests (TestDefaultModelIDEnvOverride, TestKnownModelSizesProdEntries); prereq-free outcome-classifier tests
  • The stress harness demonstrably catches the target crash class (local repro above)

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

…concurrency stress suite

Rearchitects integration CI around the models the fleet actually serves.
Qwen3.5 (retired) is gone from all defaults; every heavy job now runs
against gemma-4-26b-qat-4bit (15.6 GB) and gpt-oss-20b (12.1 GB), solo,
in parallel.

Workflow (integration.yml + new .github/actions/e2e-setup composite):
- build once (SwiftPM cache keyed off Package.resolved; metallib cached
  by mlx version so warm runs skip pip) -> artifact -> 7 fan-out jobs
- integration + stress matrices over both prod models; profile on gemma;
  PR-only benchmark matrix with a single aggregated PR comment
  (benchmark-report job merges per-model markdown artifacts)
- test jobs checkout WITHOUT submodules and use the prebuilt binary via
  DARKBLOOM_PROVIDER_BINARY/MLX_METALLIB_PATH (plus a copy into
  .build/debug so the testbed's cached-binary short-circuit holds)
- models download via the provider's real path: darkbloom models download
  <id> --coordinator https://api.darkbloom.dev — R2 CDN with per-file and
  aggregate SHA-256 verification, i.e. the exact fleet code path
- concurrency group cancels superseded runs per ref

Testbed (e2e/):
- TESTBED_MODEL / TESTBED_MODEL_GB env knobs; DefaultModelID() is the
  single resolution point (TESTBED_MODEL > legacy TESTBED_MODEL_ID >
  gpt-oss-20b default); prod model sizes registered; all 8 hardcoded
  Qwen literals in benchmark_test.go routed through it
- Provider.Alive()/ExitCode() liveness accessors (atomics fed by the
  existing wait goroutine)

NEW e2e/stress_test.go — TestStress_ConcurrentAdmission:
3 waves x 8 concurrent streams with 25% mid-stream cancels timed to race
the next wave's batch admission. Asserts: provider process survives,
zero hangs, no non-503 5xx clusters, no truncated non-victim streams,
>=80% completion, accounting integrity. Local smoke against a dev engine
build reproduced the broadcast_shapes fatal deterministically at
concurrency >=2 — exactly the crash class this suite exists to catch.
This PR's own stress legs on the master engine + prod models arbitrate
whether the shipped engine is affected.
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
d-inference Ready Ready Preview Jul 2, 2026 6:16am
d-inference-console-ui-dev Ready Ready Preview Jul 2, 2026 6:16am
d-inference-landing Ready Ready Preview Jul 2, 2026 6:16am

Request Review

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

No threat-model-covered files were changed; this PR adds E2E/integration test infrastructure and a new Swift unit test that fall entirely outside current affected_files patterns.


Trust boundaries touched

None of the existing TB-001 through TB-009 boundaries are directly modified by this PR.


Threat coverage

No T-xxx threats are affected — ✅/⚠️/ℹ️ verdicts are not applicable.


New attack surface / threat-model gaps to note

Two areas are worth a brief threat-model triage:

1. .github/actions/e2e-setup/action.yml + .github/workflows/integration.yml
The threat model explicitly excludes the GitHub Actions / R2 release supply chain from scope, so no finding is raised here. However, reviewers should confirm:

  • The workflow does not inject production secrets (coordinator API keys, Solana mnemonic, profile-signing P12, PROFILE_SIGNING_P12_B64, admin keys) into the E2E environment. If real secrets from the affected_files list (e.g. A-005, A-006, A-013) flow into CI jobs, that would re-enter scope.
  • e2e/testbed/config.go should use stub/ephemeral credentials only. If it reads from environment variables that are shared with production KMS, that is worth a separate review.

2. e2e/stress_test.go + e2e/integration_test.go
Stress and integration tests that exercise the coordinator WebSocket or provider registration path against a real (even staging) coordinator could inadvertently probe TB-002 rate-limiting gaps (SEC-034) or the device-code flow (TB-006). Confirm these tests target api.dev.darkbloom.xyz (explicitly out of scope) and cannot be redirected to production endpoints via config.

3. provider-swift/Tests/ProviderCoreTests/ChunkSenderTests.swift
This touches the SSE chunk-sending path, which is directly relevant to T-008 (plaintext fallback on encryption failure, SEC-016). The test itself doesn't change production code, so no regression is introduced, but the threat model's affected_files list for T-008 (provider-swift/Sources/ProviderCore/ProviderLoop.swift) should arguably be extended to include ChunkSenderTests.swift so future changes to this test file get automatic threat-model review.


SEC-* findings resolved

None.


Recommendation

No security action required for this PR. Suggested threat-model maintenance:

  • Add provider-swift/Tests/ProviderCoreTests/ChunkSenderTests.swift to T-008.affected_files.
  • Add e2e/testbed/config.go to a new or existing finding that tracks credential hygiene in test infrastructure, or document it as explicitly out of scope.

🔐 Threat model: docs/threat-model.yaml · Updates on each push to this PR

@ethenotethan ethenotethan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review — Layr-Labs/d-inference#

Verdict: REQUEST_CHANGES

Security — 1 finding(s) (1 blocking)

  • 🟡 [MEDIUM] .github/actions/e2e-setup/action.yml:62-64 — Hardcoded coordinator URL in shell command
    • Suggestion: Use environment variable or input parameter for coordinator URL to avoid hardcoding production endpoints

Performance — ✅ No issues found

Type_diligence — ✅ No issues found

Additive_complexity — ✅ No issues found

1 finding(s) total, 1 blocking. Verdict: REQUEST_CHANGES.

🤖 Automated review by Centaur · DAR-186

Comment thread .github/actions/e2e-setup/action.yml
@blacksmith-sh

blacksmith-sh Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
TestBenchmark_MultiModelMultiProvider View Logs
github.com/eigeninference/d-inference/e2e/TestBenchmark_HeavyLoad_100Concurrent_10KB View Logs
github.com/eigeninference/d-inference/e2e/TestBenchmark_HighConcurrency View Logs
github.com/eigeninference/d-inference/e2e/TestBenchmark_MultiModelMultiProvider View Logs
github.com/eigeninference/d-inference/e2e/TestBenchmark_SingleModelScaling View Logs
github.com/eigeninference/d-inference/e2e/TestBenchmark_SingleModelScaling/5-providers View Logs

Fix with Codesmith
Need help on this PR? Tag /codesmith with what you need.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7abbbc983b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread e2e/benchmark_test.go
runBenchmark(t, fmt.Sprintf("%d-provider-scaling", numProviders),
testbed.SuiteConfig{
ModelSpecs: []testbed.ModelSpec{{ModelID: "mlx-community/Qwen3.5-0.8B-MLX-4bit", NumProviders: numProviders}},
ModelSpecs: []testbed.ModelSpec{{ModelID: testbed.DefaultModelID(), NumProviders: numProviders}},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid fan-out benchmarks for production models

When the PR benchmark matrix sets TESTBED_MODEL to gemma-4-26b-qat-4bit or gpt-oss-20b, this subtest now runs the scaling loop with up to 5 separate provider processes for that production model. Suite.startProviders starts one darkbloom process per NumProviders, and each process loads its own model weights on first routed request, so the gemma leg can require roughly 5 copies of a 15.6 GB model instead of the single-copy coverage the new matrix is meant to exercise; this is likely to OOM or invalidate the benchmark on CI runners that can only load one production model copy. Keep the multi-provider scaling cases on the tiny model or cap provider count for production-model matrix legs.

Useful? React with 👍 / 👎.

…rameterize coordinator URL

- e2e/stress_test.go: the >=80%-of-non-cancelled completion floor was
  miscalibrated — on a 48GB virtual M4 Pro the coordinator correctly shed
  21/24 gemma-4-26b requests with 429/503 (token-budget admission working,
  not failing). Shed is now unbounded; instead, >=90% of non-cancelled
  ACCEPTED streams (2xx, stream started) must complete cleanly, and at
  least one request overall must complete. Crash detection (provider
  Alive()), zero-hang, 5xx-cluster, and truncated-stream assertions are
  unchanged.
- e2e/integration_test.go: TestIntegration_E2EEncryptionCorrectness now
  accepts non-empty content OR reasoning as proof of decryption — gpt-oss
  is a reasoning model and can spend the whole max_tokens budget on the
  reasoning channel, leaving content empty. Also bumps that test's
  max_tokens 20 -> 128 to give the final channel a chance.
- ChunkSenderTests: the coalescing test asserted 'enqueue 5 -> 1 batch',
  but ChunkBatcher coalescing is best-effort per dispatch turn and a
  loaded runner can split the batch (observed 3+2 in CI). The test now
  gates the sink on a primer frame so the remaining 4 enqueues pile up
  behind the blocked serial queue and MUST coalesce — deterministic:
  exactly [0] then [1,2,3,4], order preserved. No production changes.
- .github/actions/e2e-setup: add a coordinator-url input (default
  https://api.darkbloom.dev) consumed via env-var indirection instead of
  a hardcoded URL interpolated into the script body (review 3510753763).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47e4f9859f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +120 to +121
go test ./e2e/ -count=1 -v -timeout 30m -p=1 \
-run 'TestIntegration'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse a warmed provider for production-model integration

When this matrix leg sets TESTBED_MODEL to gemma-4-26b-qat-4bit or gpt-oss-20b, this command runs every TestIntegration_*; those tests call startSuite, which starts a fresh provider process per test, and the provider lazily loads the selected model on the first routed request. That makes a single integration leg cold-load the 12–16 GB production model for nearly every integration test, so the 30m Go timeout/40m job timeout can be consumed by repeated model loads instead of test coverage. Please either keep the full integration suite on a tiny model and add a small production-model smoke, or share one warmed suite/provider for the production-model job.

Useful? React with 👍 / 👎.

@ethenotethan ethenotethan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review — Layr-Labs/d-inference#

Verdict: REQUEST_CHANGES

Security — 1 finding(s) (1 blocking)

  • 🟡 [MEDIUM] .github/actions/e2e-setup/action.yml:64-73 — Shell injection via environment variables in model download command
    • Suggestion: Use array-based command execution or properly quote/escape the variables in the shell command

Performance — 1 finding(s) (1 blocking)

  • 🟡 [MEDIUM] e2e/stress_test.go:89 — Stress test launches 24 goroutines without context cancellation or errgroup
    • Suggestion: Use errgroup.WithContext or add proper context cancellation to the goroutines in startStressWave to prevent goroutine leaks if the test context is cancelled

Type_diligence — ✅ No issues found

Additive_complexity — ✅ No issues found

2 finding(s) total, 2 blocking. Verdict: REQUEST_CHANGES.

🤖 Automated review by Centaur · DAR-186

Comment on lines +64 to +73
env:
# Env-var indirection (not raw ${{ }} interpolation into the script
# body) so the input values are never parsed as shell.
MODEL_ID: ${{ inputs.model }}
COORDINATOR_URL: ${{ inputs.coordinator-url }}
run: |
# Zero-config: `models download` falls back to hardware defaults when
# no ~/.darkbloom config exists, and --coordinator overrides the URL
# (see provider-swift/Sources/darkbloom/ModelsCommand.swift).
/tmp/provider-artifact/darkbloom models download "$MODEL_ID" \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [MEDIUM] 🔒 Shell injection via environment variables in model download command

💡 Suggestion: Use array-based command execution or properly quote/escape the variables in the shell command

📊 Score: 3×4 = 12 · Category: command-injection

Comment thread e2e/stress_test.go
ctx := context.Background()
s := testbed.NewSuite(testbed.SuiteConfig{
NumUsers: 1,
SeedBalance: 500_000_000,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [MEDIUM] ⚡ Stress test launches 24 goroutines without context cancellation or errgroup

💡 Suggestion: Use errgroup.WithContext or add proper context cancellation to the goroutines in startStressWave to prevent goroutine leaks if the test context is cancelled

📊 Score: 3×4 = 12 · Category: unbounded_goroutines

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants