Skip to content

fix(provider): bound weight hash memory growth - #358

Merged
Gajesh2007 merged 1 commit into
masterfrom
fix-model-download-hash-oom
Jun 15, 2026
Merged

Gajesh2007 merged 1 commit into
masterfrom
fix-model-download-hash-oom

Conversation

@Gajesh2007

@Gajesh2007 Gajesh2007 commented Jun 15, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes provider-side weight hash verification memory growth during large model downloads.

The issue was in WeightHasher: FileHandle.read(upToCount:) returns autoreleased Data, but the loop did not drain an autorelease pool per chunk. During aggregate hash verification, every model shard could be hashed concurrently, so a large manifest-backed model such as gemma-4-26b could retain shard-scale data until the hash completed and get jetsam-killed after download progress reached 100%.

Changes

  • Drain autoreleased chunk Data inside the streaming FileHandle read loop.
  • Add bounded fan-out for aggregate file hashing so verification does not start every shard at once.
  • Add an InputStream streaming fallback before the last-resort Data(contentsOf:) fallback.
  • Add a memory regression test that hashes a 192 MiB sparse weight file and asserts peak RSS growth remains bounded.

Validation

  • swift test --filter ProviderCoreFoundationTests
    Passed: 42 tests, 0 failures.
  • swift test --filter ProviderCoreTests.ModelPrefetchDownloaderTests
    Passed: 11 tests, 0 failures.

TDD evidence: before the fix, the new memory regression failed with RSS growth of 404,176,896 bytes; after the fix, the same test passed.


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

@vercel

vercel Bot commented Jun 15, 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 Jun 15, 2026 8:53pm
d-inference-console-ui-dev Ready Ready Preview Jun 15, 2026 8:53pm
d-inference-landing Ready Ready Preview Jun 15, 2026 8:53pm

Request Review

@github-actions

github-actions Bot commented Jun 15, 2026 •

Copy link
Copy Markdown
Contributor

This PR hardens the weight-hashing path against unbounded memory allocation during the whole-file fallback and removes the concurrent parallel-dispatch implementation, but it does not change the coordinator's fail-open enforcement of weight hashes (SEC-007), so T-007, T-012, and T-027 remain open at the system level.


Trust Boundaries Touched

  • TB-003 — Provider operator vs. process (weight file integrity, memory safety of the hashing path)
  • TB-007 — Provider inference engine (weight hash advertised at registration and heartbeat)

Threat Analysis

T-007 · T-012 · T-027 — Weight hash integrity (Tampering / Information Disclosure)

⚠️ Neutral-to-positive at the provider layer; system-level status unchanged.

The hash value produced by this code is unchanged for all files that fit within the streaming paths (the dominant case for every model shard). The coordinator still receives the same hash and still enforces it fail-open (SEC-007: omitting weight_hash passes admission). This PR improves the reliability and memory safety of the hashing computation itself, but does not close the coordinator-side enforcement gap.

Specific observations:

  • WeightHasher.swift lines 112–140 — parallel dispatch removed. The DispatchGroup + UnsafeMutablePointer concurrent write pattern is replaced by a sequential loop. The original pattern was annotated nonisolated(unsafe) with a manual barrier. Removing it eliminates a class of subtle data-race bugs (e.g., if group.wait() were ever removed by a later refactor). Positive hardening of the hashing path's correctness. However, hashing is now strictly sequential; for providers with many large shards this is a latency regression at startup/heartbeat time. Not a security issue, but worth flagging for operator UX.

  • WeightHasher.swift lines 130–154 — whole-file fallback now gated by maxWholeFileFallbackBytes (64 MiB). Previously Data(contentsOf:) was called unconditionally for any file where FileHandle and (now) InputStream failed, meaning a multi-GB shard landing in the NSFileProtectionComplete fallback path would be read entirely into process memory. The new guard at line 136–138 returns nil for large files rather than OOM-ing. This is a meaningful hardening against an operator who could craft a scenario where FileHandle and InputStream both fail on a large file (e.g., via extended attribute manipulation), previously forcing the process to allocate gigabytes. The nil return propagates up to hashFilesWithRelativeKey as a hash failure, which the coordinator should treat as a missing hash — but again, fail-open (SEC-007) means the provider is still admitted.

  • WeightHasher.swift lines 203–237 — new hashSingleFileViaInputStream path. The InputStream fallback streams correctly in bufferSize-sized chunks (64 KB). The isStreamingBufferSizeAllowed guard at line 205 is a belt-and-suspenders check that the buffer constant hasn't drifted above 1 MiB. No security concern here.

  • fileSizeBytes(at:) uses resolvingSymlinksInPath() (line 162). This is correct — a symlink pointing to a large file is still a large file. However, note that FileManager.attributesOfItem(atPath:) follows symlinks, so this is redundant but harmless.

  • withAutoreleasePool wrapper (lines 174–180) applied to handle.read(upToCount:) and the Data(contentsOf:) call. This prevents NSData temporary objects accumulating across a long streaming loop. Positive memory hygiene.

T-027 — WeightHasherMemoryTests.swift (new test file)

✅ Strengthens the mitigation.

  • testWholeFileFallbackRejectsLargeModelShards directly validates the 64 MiB cap. This is exactly the regression guard needed so a future developer cannot accidentally raise maxWholeFileFallbackBytes without a test failure.
  • testLargeFileHashingDoesNotRetainEveryChunk measures RSS growth against a 64 MiB budget while hashing a 192 MiB file. This catches any reintroduction of whole-file buffering in the streaming path. Good. One caveat: ru_maxrss on macOS returns peak resident set since process start, not delta. The test computes afterPeak - beforePeak (line 55), which is correct only if nothing else in the test process has a higher peak between the two getrusage calls. In a shared test runner that runs other large-memory tests first, beforePeak will already reflect the prior high-water mark and the delta will undercount true growth. This is a test reliability issue, not a security issue, but it means the RSS assertion can silently pass even if the new code is leaky.

New Attack Surface Not Covered by Existing Threats

None introduced. The InputStream fallback is internal to the hashing path, uses no network or IPC, and does not change the hash value for any file the existing paths already handle.


Open Findings Status

Finding Status after this PR
SEC-007 Still open — coordinator fail-open enforcement is untouched by this PR

No SEC-* findings are resolved by this change.


Summary for Reviewer

The coordinator-side fix (SEC-007: make weight hash enforcement fail-closed) is the missing half of this work. Until that lands, an operator can still omit weight_hash entirely and be admitted. This PR makes the provider's computation of that hash more memory-safe and reliable, which is necessary but not sufficient to close T-007/T-012/T-027.


🔐 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 — ✅ No issues found

Performance — 3 finding(s) (2 blocking)

  • 🔴 [CRITICAL] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:127-142 — Semaphore-based concurrency control may cause goroutine buildup under high load
    • Suggestion: Consider using errgroup.Group with SetLimit() or a worker pool pattern to bound concurrent operations more efficiently
  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:182 — Data(contentsOf:) loads entire file into memory as fallback
    • Suggestion: Move this fallback inside the semaphore-controlled section or add file size checks to prevent loading very large files entirely into memory
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:115 — UnsafeMutablePointer allocation scales with file count without bounds
    • Suggestion: Add a maximum file count check or use a different data structure to prevent excessive memory allocation for very large manifests

Type_diligence — ✅ No issues found

Additive_complexity — 1 finding(s)

  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:205-238 — hashSingleFileViaInputStream duplicates streaming pattern from hashSingleFileViaHandle
    • Suggestion: Extract common streaming hash pattern into shared helper function to reduce code duplication and maintenance burden

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

🤖 Automated review by Centaur · DAR-186

Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift Outdated
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift Outdated
@Gajesh2007 Gajesh2007 changed the title [codex] fix provider weight hash memory growth fix(provider): bound weight hash memory growth Jun 15, 2026
@Gajesh2007
Gajesh2007 force-pushed the fix-model-download-hash-oom branch from ac02c27 to abd5433 Compare June 15, 2026 20:29

@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 — ✅ No issues found

Performance — 3 finding(s) (3 blocking)

  • 🔴 [CRITICAL] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:110-149 — Semaphore-based concurrency control may cause goroutine buildup under high load
    • Suggestion: Consider using errgroup.Group with SetLimit() or worker pool pattern to bound concurrent operations more efficiently
  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:182-184 — Data(contentsOf:) loads entire file into memory as fallback
    • Suggestion: Move this fallback inside the semaphore-controlled section or add file size checks to prevent loading very large files entirely into memory
  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:207-208 — Fixed-size buffer allocation without size validation
    • Suggestion: Add validation that bufferSize is reasonable or make buffer size configurable based on available memory

Type_diligence — ✅ No issues found

Additive_complexity — 1 finding(s)

  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:205-238 — hashSingleFileViaInputStream duplicates streaming pattern from hashSingleFileViaHandle
    • Suggestion: Extract common streaming hash pattern into shared helper function to reduce code duplication and maintenance burden

4 finding(s) total, 3 blocking. Verdict: REQUEST_CHANGES.

🤖 Automated review by Centaur · DAR-186

Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift

@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: ac02c27885

ℹ️ 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".

// Last-resort compatibility fallback: NSData can handle file-protection
// cases that raw POSIX open() rejects. Fan-out is capped above so this
// cannot load every shard at once.
guard let data = autoreleasepool(invoking: { try? Data(contentsOf: url) }) else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep WeightHasher buildable on Linux

On Linux builds of ProviderCoreFoundation (for example the Linux/GCP darkbloom-publish path used by scripts/publish-model.sh), Swift does not provide autoreleasepool, so this new unguarded call makes the target fail to compile before any hashing runs; I verified with the Linux Swift toolchain error cannot find 'autoreleasepool' in scope. The same call in the read loop below has the same issue, so this needs a Linux-compatible wrapper/no-op path rather than using autoreleasepool directly.

Useful? React with 👍 / 👎.

private static func maxRSSBytes() throws -> UInt64 {
#if canImport(Darwin) || canImport(Glibc)
var usage = rusage()
guard getrusage(RUSAGE_SELF, &usage) == 0 else {

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 Pass the Glibc getrusage selector as Int32

On Linux, Glibc exposes RUSAGE_SELF as __rusage_who while getrusage expects __rusage_who_t/Int32; building this new ProviderCoreFoundationTests file with Swift 6.2.4 fails at this line with cannot convert value of type '__rusage_who' to expected argument type '__rusage_who_t'. Since this test target is meant to stay Linux-buildable, use the raw value/cast under Glibc so the suite can compile there.

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 — ✅ No issues found

Performance — 1 finding(s) (1 blocking)

  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:125-145 — Worker-based parallelism creates uneven work distribution for file hashing
    • Suggestion: Use a work-stealing queue or TaskGroup instead of static worker assignment to ensure balanced load distribution across files of varying sizes

Type_diligence — 1 finding(s) (1 blocking)

  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:207-225 — Multiple unsafe type casts from Any to numeric types without checking cast success
    • Suggestion: Use guard let with type assertions: guard let size = rawSize as? NSNumber else { return nil }. Current code could panic if FileManager returns unexpected types for .size attribute

Additive_complexity — 3 finding(s)

  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:194-215 — fileSizeBytes function handles multiple type conversions unnecessarily
    • Suggestion: Use FileManager.default.attributesOfItem(atPath:)[.size] as? UInt64 directly, or simplify with a single NSNumber conversion since all numeric types can convert through NSNumber
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:193-196 — isWholeFileFallbackAllowed is a trivial one-liner extracted as separate function
    • Suggestion: Inline this simple comparison directly where used, or keep as private helper if used multiple times
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:239-267 — hashSingleFileViaInputStream duplicates streaming pattern from FileHandle version
    • Suggestion: Extract common streaming hash logic into shared helper function that accepts a read closure, reducing duplication between FileHandle and InputStream paths

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

🤖 Automated review by Centaur · DAR-186

Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift Outdated
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift Outdated
Comment on lines +194 to +215
static func isWholeFileFallbackAllowed(fileSizeBytes: UInt64) -> Bool {
fileSizeBytes <= maxWholeFileFallbackBytes
}

private static func fileSizeBytes(at url: URL) -> UInt64? {
let path = url.resolvingSymlinksInPath().path
guard let rawSize = try? FileManager.default.attributesOfItem(atPath: path)[.size] else {
return nil
}
if let size = rawSize as? NSNumber {
return size.uint64Value
}
if let size = rawSize as? UInt64 {
return size
}
if let size = rawSize as? Int64, size >= 0 {
return UInt64(size)
}
if let size = rawSize as? Int, size >= 0 {
return UInt64(size)
}
return nil

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.

🔵 [INFO] 🧩 fileSizeBytes function handles multiple type conversions unnecessarily

💡 Suggestion: Use FileManager.default.attributesOfItem(atPath:)[.size] as? UInt64 directly, or simplify with a single NSNumber conversion since all numeric types can convert through NSNumber

📊 Score: 2×3 = 6 · Category: over-abstraction

Comment on lines 193 to +196

static func isWholeFileFallbackAllowed(fileSizeBytes: UInt64) -> Bool {
fileSizeBytes <= maxWholeFileFallbackBytes
}

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.

🔵 [INFO] 🧩 isWholeFileFallbackAllowed is a trivial one-liner extracted as separate function

💡 Suggestion: Inline this simple comparison directly where used, or keep as private helper if used multiple times

📊 Score: 1×2 = 2 · Category: misplaced responsibility

Comment on lines +239 to +267
private static func hashSingleFileViaInputStream(at url: URL) -> SHA256Digest? {
guard let stream = InputStream(url: url) else {
return nil
}
stream.open()
defer { stream.close() }

var hasher = SHA256()
var buffer = [UInt8](repeating: 0, count: bufferSize)

while true {
let bytesRead = buffer.withUnsafeMutableBufferPointer { ptr -> Int in
guard let baseAddress = ptr.baseAddress else { return -1 }
return stream.read(baseAddress, maxLength: bufferSize)
}
if bytesRead < 0 {
return nil
}
if bytesRead == 0 {
break
}
buffer.withUnsafeBufferPointer { ptr in
guard let baseAddress = ptr.baseAddress else { return }
hasher.update(bufferPointer: UnsafeRawBufferPointer(start: baseAddress, count: bytesRead))
}
}

return hasher.finalize()
}

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.

🔵 [INFO] 🧩 hashSingleFileViaInputStream duplicates streaming pattern from FileHandle version

💡 Suggestion: Extract common streaming hash logic into shared helper function that accepts a read closure, reducing duplication between FileHandle and InputStream paths

📊 Score: 2×3 = 6 · Category: duplicate logic

@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 — ✅ No issues found

Performance — 3 finding(s) (2 blocking)

  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:135-147 — Sequential worker dispatch creates uneven load distribution
    • Suggestion: Use a work-stealing queue or TaskGroup for better load balancing across workers
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:216-228 — Multiple type checks and casts for file size attribute
    • Suggestion: Use a single switch statement or guard-let chain to avoid redundant type checking
  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:264-266 — Fixed-size buffer allocation without validation
    • Suggestion: Pre-validate bufferSize is within bounds before allocating the buffer array

Type_diligence — 2 finding(s) (1 blocking)

  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:116 — fileSizeBytes(at:) returns optional UInt64 but rawSize is cast without type assertion
    • Suggestion: Add proper type assertion with ok-checking: if let size = rawSize as? NSNumber { return size.uint64Value } else if let size = rawSize as? UInt64 { return size } // etc
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:205-228 — Multiple unsafe type casts in fileSizeBytes without checking cast success
    • Suggestion: Replace force casts with safe type assertions: guard let size = rawSize as? NSNumber else { /* try next type */ }

Additive_complexity — 5 finding(s) (1 blocking)

  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:147 — Worker count calculation could be simplified
    • Suggestion: Replace max(1, min(maxConcurrentFileHashes, count)) with just min(maxConcurrentFileHashes, count) since min() already handles the zero case naturally
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:196-200 — Single-use helper function adds unnecessary indirection
    • Suggestion: Inline isWholeFileFallbackAllowed at its single call site since it's just a simple comparison
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:202-206 — Single-use helper function adds unnecessary indirection
    • Suggestion: Inline isStreamingBufferSizeAllowed at its single call site since it's just a simple bounds check
  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:208-237 — Complex file size extraction reimplements Foundation functionality
    • Suggestion: Use try url.resourceValues(forKeys: [.fileSizeKey]).fileSize instead of manual attribute parsing with multiple type checks
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:239-245 — Conditional compilation wrapper for single autoreleasepool call
    • Suggestion: Use autoreleasepool directly since it's available on all Swift platforms; the #if canImport(ObjectiveC) check is unnecessary complexity

10 finding(s) total, 4 blocking. Verdict: REQUEST_CHANGES.

🤖 Automated review by Centaur · DAR-186

Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift Outdated
Comment on lines +216 to +228
return UInt64(size)
}
if let size = rawSize as? Int, size >= 0 {
return UInt64(size)
}
return nil
}

private static func withAutoreleasePool<T>(_ body: () -> T) -> T {
#if canImport(ObjectiveC)
return autoreleasepool(invoking: body)
#else
return body()

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.

🔵 [INFO] ⚡ Multiple type checks and casts for file size attribute

💡 Suggestion: Use a single switch statement or guard-let chain to avoid redundant type checking

📊 Score: 2×3 = 6 · Category: Repeated work

Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift Outdated
Comment on lines +196 to +200
static func isWholeFileFallbackAllowed(fileSizeBytes: UInt64) -> Bool {
fileSizeBytes <= maxWholeFileFallbackBytes
}

static func isStreamingBufferSizeAllowed(_ bytes: Int) -> Bool {

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.

🔵 [INFO] 🧩 Single-use helper function adds unnecessary indirection

💡 Suggestion: Inline isWholeFileFallbackAllowed at its single call site since it's just a simple comparison

📊 Score: 2×2 = 4 · Category: over-abstraction

Comment on lines +202 to +206
}

private static func fileSizeBytes(at url: URL) -> UInt64? {
let path = url.resolvingSymlinksInPath().path
guard let rawSize = try? FileManager.default.attributesOfItem(atPath: path)[.size] else {

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.

🔵 [INFO] 🧩 Single-use helper function adds unnecessary indirection

💡 Suggestion: Inline isStreamingBufferSizeAllowed at its single call site since it's just a simple bounds check

📊 Score: 2×2 = 4 · Category: over-abstraction

@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 — ✅ No issues found

Performance — 3 finding(s) (3 blocking)

  • 🔴 [CRITICAL] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:114-162 — Worker goroutines access shared state without proper synchronization
    • Suggestion: Replace NSLock with atomic operations or use a proper work-stealing queue implementation to avoid lock contention and potential race conditions in the shared index access pattern
  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:130-138 — Manual unsafe pointer management with complex deallocation logic
    • Suggestion: Use Swift's built-in concurrent collections or TaskGroup for cleaner memory management and better performance than manual unsafe pointer allocation
  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:268-287 — Fixed-size buffer allocation in InputStream path without size validation
    • Suggestion: Validate bufferSize against maxStreamingBufferSize before allocating the buffer array to prevent potential large allocations

Type_diligence — ✅ No issues found

Additive_complexity — 3 finding(s) (1 blocking)

  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:125-140 — Complex worker pool with manual memory management for bounded parallelism
    • Suggestion: Use TaskGroup or OperationQueue instead of manual unsafe pointers and locks for cleaner concurrency
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:228-236 — Custom autoreleasepool wrapper when Foundation already provides this
    • Suggestion: Use Foundation's autoreleasepool directly instead of custom conditional compilation wrapper
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:257-289 — InputStream hashing duplicates FileHandle streaming pattern
    • Suggestion: Extract common streaming hash logic to reduce duplication between FileHandle and InputStream paths

6 finding(s) total, 4 blocking. Verdict: REQUEST_CHANGES.

🤖 Automated review by Centaur · DAR-186

Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift Outdated
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift Outdated
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift
Comment thread provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift Outdated
Comment on lines +228 to 236
private static func withAutoreleasePool<T>(_ body: () -> T) -> T {
#if canImport(ObjectiveC)
return autoreleasepool(invoking: body)
#else
return body()
#endif
}

private static func hashSingleFileViaHandle(at url: URL) -> SHA256Digest? {

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.

🔵 [INFO] 🧩 Custom autoreleasepool wrapper when Foundation already provides this

💡 Suggestion: Use Foundation's autoreleasepool directly instead of custom conditional compilation wrapper

📊 Score: 2×3 = 6 · Category: reinventing stdlib

Comment on lines +257 to 289
private static func hashSingleFileViaInputStream(at url: URL) -> SHA256Digest? {
guard isStreamingBufferSizeAllowed(bufferSize) else {
return nil
}
guard let stream = InputStream(url: url) else {
return nil
}
stream.open()
defer { stream.close() }

var hasher = SHA256()
var buffer = [UInt8](repeating: 0, count: bufferSize)

while true {
let bytesRead = buffer.withUnsafeMutableBufferPointer { ptr -> Int in
guard let baseAddress = ptr.baseAddress else { return -1 }
return stream.read(baseAddress, maxLength: bufferSize)
}
if bytesRead < 0 {
return nil
}
if bytesRead == 0 {
break
}
buffer.withUnsafeBufferPointer { ptr in
guard let baseAddress = ptr.baseAddress else { return }
hasher.update(bufferPointer: UnsafeRawBufferPointer(start: baseAddress, count: bytesRead))
}
}

return hasher.finalize()
}
}

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.

🔵 [INFO] 🧩 InputStream hashing duplicates FileHandle streaming pattern

💡 Suggestion: Extract common streaming hash logic to reduce duplication between FileHandle and InputStream paths

📊 Score: 2×2 = 4 · Category: duplicate logic

@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: 16fb4d584a

ℹ️ 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 +198 to +199
guard let size = fileSizeBytes(at: url), isWholeFileFallbackAllowed(fileSizeBytes: size) else {
return nil

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 Keep large-shard fallback bounded at callers

When a shard larger than 64 MiB hits the documented fallback path where the streaming readers fail, this guard makes WeightHasher.hashSingleFile return nil; however the manifest download verifier still treats that as a signal to run its own Data(contentsOf:) fallback (ModelCatalog.swift:1096-1099 and 1288-1295). That means large URLSession-downloaded model files can still be read wholesale into memory during verification, just one frame higher, so the memory cap added here does not actually protect the download paths that call through sha256Hex.

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 — ✅ No issues found

Performance — 3 finding(s) (3 blocking)

  • 🔴 [CRITICAL] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:112-122 — Removed parallel file hashing without bounded concurrency control
    • Suggestion: The original parallel implementation had unbounded goroutines but provided better throughput. Consider adding bounded concurrency (e.g., semaphore with worker pool) instead of removing parallelism entirely for large model manifests.
  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:184-195 — Autoreleasepool wrapping every FileHandle.read() call adds overhead
    • Suggestion: Move autoreleasepool outside the read loop to drain accumulated objects periodically (e.g., every 100 iterations) rather than on every single read operation.
  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:147-156 — Whole-file Data(contentsOf:) fallback can block for large files
    • Suggestion: The size check helps but Data(contentsOf:) still blocks the calling thread. Consider making this async or moving to a background queue for files approaching the 64MB limit.

Type_diligence — ✅ No issues found

Additive_complexity — 4 finding(s) (1 blocking)

  • 🟡 [MEDIUM] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:112-125 — Removed parallel file hashing without clear justification
    • Suggestion: Consider keeping bounded parallelism (e.g., TaskGroup with limited concurrency) rather than fully sequential processing for large model shards
  • 🔵 [INFO] provider-swift/Sources/ProviderCore/Models/ModelCatalog.swift:1497-1503 — Unnecessary wrapper function with single use case
    • Suggestion: Inline sha256HexForVerification calls directly or remove the hasher parameter version if only used in tests
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:178-184 — Custom autoreleasepool wrapper when Foundation already provides this
    • Suggestion: Use Foundation's autoreleasepool directly instead of custom wrapper with conditional compilation
  • 🔵 [INFO] provider-swift/Sources/ProviderCoreFoundation/WeightHasher.swift:159-165 — Multiple buffer size validation functions for simple bounds check
    • Suggestion: Combine into single validation function or inline the simple bounds checks

7 finding(s) total, 4 blocking. Verdict: REQUEST_CHANGES.

🤖 Automated review by Centaur · DAR-186

Comment on lines 184 to +195
@@ -180,7 +192,7 @@ public struct WeightHasher: Sendable {
var hasher = SHA256()

while true {
guard let chunk = try? handle.read(upToCount: bufferSize) else {
guard let chunk = withAutoreleasePool({ try? handle.read(upToCount: bufferSize) }) else {

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] ⚡ Autoreleasepool wrapping every FileHandle.read() call adds overhead

💡 Suggestion: Move autoreleasepool outside the read loop to drain accumulated objects periodically (e.g., every 100 iterations) rather than on every single read operation.

📊 Score: 2×5 = 10 · Category: Blocking I/O on hot paths

Comment on lines +147 to 156
// shard cannot be copied into memory after both streaming paths fail.
guard let size = fileSizeBytes(at: url), isWholeFileFallbackAllowed(fileSizeBytes: size) else {
return nil
}
guard let data = withAutoreleasePool({ try? Data(contentsOf: url) }) else {
return nil
}
var hasher = SHA256()
hasher.update(data: data)
return hasher.finalize()

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] ⚡ Whole-file Data(contentsOf:) fallback can block for large files

💡 Suggestion: The size check helps but Data(contentsOf:) still blocks the calling thread. Consider making this async or moving to a background queue for files approaching the 64MB limit.

📊 Score: 3×3 = 9 · Category: Blocking I/O on hot paths

Comment on lines +1497 to +1503
static func sha256HexForVerification(of url: URL) -> String {
sha256HexForVerification(of: url, hasher: sha256Hex)
}

static func sha256HexForVerification(of url: URL, hasher: (URL) -> String?) -> String {
hasher(url) ?? "<unreadable>"
}

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.

🔵 [INFO] 🧩 Unnecessary wrapper function with single use case

💡 Suggestion: Inline sha256HexForVerification calls directly or remove the hasher parameter version if only used in tests

📊 Score: 2×3 = 6 · Category: over-abstraction

Comment on lines +178 to +184
private static func withAutoreleasePool<T>(_ body: () -> T) -> T {
#if canImport(ObjectiveC)
return autoreleasepool(invoking: body)
#else
return body()
#endif
}

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.

🔵 [INFO] 🧩 Custom autoreleasepool wrapper when Foundation already provides this

💡 Suggestion: Use Foundation's autoreleasepool directly instead of custom wrapper with conditional compilation

📊 Score: 1×2 = 2 · Category: reinventing stdlib

Comment on lines +159 to +165
static func isWholeFileFallbackAllowed(fileSizeBytes: UInt64) -> Bool {
fileSizeBytes <= maxWholeFileFallbackBytes
}

static func isStreamingBufferSizeAllowed(_ bytes: Int) -> Bool {
bytes > 0 && bytes <= maxStreamingBufferSize
}

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.

🔵 [INFO] 🧩 Multiple buffer size validation functions for simple bounds check

💡 Suggestion: Combine into single validation function or inline the simple bounds checks

📊 Score: 2×3 = 6 · Category: over-configuration

@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: ec1590e1bb

ℹ️ 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 +148 to +149
guard let size = fileSizeBytes(at: url), isWholeFileFallbackAllowed(fileSizeBytes: size) else {
return nil

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 Keep protected large shards verifiable

When a large model shard reaches the file-protection fallback described above (i.e. both streaming readers fail), this guard returns nil before the Data(contentsOf:) compatibility path can run. With the caller-side Data fallback now removed in ModelCatalog.sha256HexForVerification, that nil becomes <unreadable>, causing manifest verification to delete/retry the shard and ultimately fail for URLSession-downloaded shards larger than 64 MiB that need the Foundation/file-coordinated fallback. This needs a bounded Foundation-compatible streaming path rather than rejecting those large files outright.

Useful? React with 👍 / 👎.

@Gajesh2007
Gajesh2007 merged commit f24f8b1 into master Jun 15, 2026
107 of 110 checks passed
@Gajesh2007
Gajesh2007 deleted the fix-model-download-hash-oom branch June 15, 2026 21:51
jonathan308 added a commit that referenced this pull request Oct 24, 2026
…x ff4bb35b)

libs/mlx-swift-lm moves efd38585 -> d4fd516e, mlx-swift-lm #356's head
after it merges SDK main 476cb7c0. Main gained #358, which pins
Layr-Labs/mlx-swift eedca444 (was 6c31f9f1). That mlx-swift pins mlx
ff4bb35b and mlx-c 004c735f, which bring two fixes:

- float16 and bfloat16 floor divide computed in float32
  (Layr-Labs/mlx#40; CPU and the Metal binary_ops kernel);
- a freed compiled closure erased from every thread's compile cache
  (Layr-Labs/mlx#43).

libs/mlx-swift moves to eedca444 and libs/mlx to ff4bb35b in lockstep,
so the metallib is rebuilt from the new kernels. No Swift API changed:
the SDK's own change is Package.swift only, and mlx-swift changes its
pins and regenerated sources.
jonathan308 added a commit that referenced this pull request Oct 24, 2026
The release now pins mlx-swift-lm d4fd516e (#356 with main's #358: mlx-swift eedca444, mlx ff4bb35b). libs/mlx-swift-lm resolves to 4d962e39 (mlx-swift-lm fix/mtp-speed): d4fd516e plus the Super MTP fix and its policy-test update.
jonathan308 added a commit that referenced this pull request Oct 24, 2026
…358) into perf/decoder-protection-0.10.2

The release branch moved libs/mlx (ff4bb35b), libs/mlx-swift (eedca444) and
libs/mlx-swift-lm (d4fd516e: the provider 0.10.2 SDK with main's #358).
libs/mlx-swift-lm conflicts with this branch's #359 fold (cad9d811); it now
points at 5d3bf208, which merges d4fd516e into that fold, so the SDK is the
release branch's d4fd516e plus #359.

This branch had an error being deployed

1 failed and 3 active deployments
Preview – d-inference-console-ui-dev — ec1590e1 Deployed Jun 15, 2026 by vercel[bot]
Preview – d-inference — ec1590e1 Deployed Jun 15, 2026 by vercel[bot]
Preview – d-inference-landing — ec1590e1 Deployed Jun 15, 2026 by vercel[bot]
benchmarks — ec1590e1 Deployed Jun 15, 2026 by Gajesh2007 via E2E Benchmarks #958
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