Skip to content

Commit 3865124

Browse files
committed
Add Xet support for faster downloads
1 parent 43c25ac commit 3865124

File tree

5 files changed

+164
-0
lines changed

5 files changed

+164
-0
lines changed

Package.resolved

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package@swift-6.1.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// swift-tools-version: 6.1
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "swift-huggingface",
8+
platforms: [
9+
.macOS(.v14),
10+
.macCatalyst(.v14),
11+
.iOS(.v16),
12+
.watchOS(.v10),
13+
.tvOS(.v17),
14+
.visionOS(.v1),
15+
],
16+
products: [
17+
.library(
18+
name: "HuggingFace",
19+
targets: ["HuggingFace"]
20+
)
21+
],
22+
dependencies: [
23+
.package(url: "https://github.com/mattt/EventSource.git", from: "1.0.0"),
24+
.package(url: "https://github.com/mattt/swift-xet.git", from: "0.1.0")
25+
],
26+
traits: [
27+
.default(
28+
enabledTraits: ["XetSupport"]
29+
),
30+
.init(
31+
name: "XetSupport",
32+
description: "Enable Xet for faster downloads",
33+
enabledTraits: []
34+
)
35+
],
36+
targets: [
37+
.target(
38+
name: "HuggingFace",
39+
dependencies: [
40+
.product(name: "EventSource", package: "EventSource"),
41+
.product(name: "Xet", package: "swift-xet", condition: .when(traits: ["XetSupport"]))
42+
],
43+
path: "Sources/HuggingFace"
44+
),
45+
.testTarget(
46+
name: "HuggingFaceTests",
47+
dependencies: ["HuggingFace"]
48+
),
49+
]
50+
)
51+

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,3 +1000,8 @@ let response = try await client.speechToText(
10001000

10011001
print("Transcription: \(response.text)")
10021002
```
1003+
1004+
## License
1005+
1006+
This project is available under the MIT license.
1007+
See the LICENSE file for more info.

Sources/HuggingFace/Hub/HubClient+Files.swift

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import UniformTypeIdentifiers
66
import FoundationNetworking
77
#endif
88

9+
#if canImport(Xet)
10+
import Xet
11+
#endif
12+
913
// MARK: - Upload Operations
1014

1115
public extension HubClient {
@@ -183,6 +187,22 @@ public extension HubClient {
183187
useRaw: Bool = false,
184188
cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
185189
) async throws -> Data {
190+
#if canImport(Xet)
191+
// Try Xet first for faster downloads
192+
do {
193+
let xetClient = try await createXetClient()
194+
let content = try xetClient.getFileContent(
195+
repo: repo.rawValue,
196+
path: repoPath,
197+
revision: revision
198+
)
199+
return content
200+
} catch {
201+
// Fallback to LFS if Xet fails
202+
}
203+
#endif
204+
205+
// Fallback to existing LFS download method
186206
let endpoint = useRaw ? "raw" : "resolve"
187207
let urlPath = "/\(repo)/\(endpoint)/\(revision)/\(repoPath)"
188208
var request = try httpClient.createRequest(.get, urlPath)
@@ -215,6 +235,28 @@ public extension HubClient {
215235
cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy,
216236
progress: Progress? = nil
217237
) async throws -> URL {
238+
#if canImport(Xet)
239+
// Try Xet first for faster downloads
240+
do {
241+
let xetClient = try await createXetClient()
242+
try xetClient.downloadFile(
243+
repo: repo.rawValue,
244+
path: repoPath,
245+
destination: destination.path,
246+
revision: revision
247+
)
248+
249+
// Update progress if provided
250+
progress?.totalUnitCount = 100
251+
progress?.completedUnitCount = 100
252+
253+
return destination
254+
} catch {
255+
// Fallback to LFS if Xet fails
256+
}
257+
#endif
258+
259+
// Fallback to existing LFS download method
218260
let endpoint = useRaw ? "raw" : "resolve"
219261
let urlPath = "/\(repo)/\(endpoint)/\(revision)/\(repoPath)"
220262
var request = try httpClient.createRequest(.get, urlPath)
@@ -513,6 +555,36 @@ public extension HubClient {
513555
continue
514556
}
515557

558+
#if canImport(Xet)
559+
// Try Xet first if available
560+
do {
561+
let xetClient = try await createXetClient()
562+
try xetClient.downloadFile(
563+
repo: repo.rawValue,
564+
path: filename,
565+
destination: fileDestination.path,
566+
revision: revision
567+
)
568+
fileProgress.completedUnitCount = 100
569+
570+
if let etag = remoteFile.etag, let revision = remoteFile.revision {
571+
try writeDownloadMetadata(
572+
commitHash: revision,
573+
etag: etag,
574+
to: metadataDestination
575+
)
576+
}
577+
578+
if Task.isCancelled {
579+
return repoDestination
580+
}
581+
582+
continue
583+
} catch {
584+
// Fallback to LFS if Xet fails
585+
}
586+
#endif
587+
516588
_ = try await downloadFile(
517589
at: filename,
518590
from: repo,

Sources/HuggingFace/Hub/HubClient.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import Foundation
44
import FoundationNetworking
55
#endif
66

7+
#if canImport(Xet)
8+
import Xet
9+
#endif
10+
711
/// A Hugging Face Hub API client.
812
///
913
/// This client provides methods to interact with the Hugging Face Hub API,
@@ -134,4 +138,21 @@ public final class HubClient: Sendable {
134138
}
135139
return defaultHost
136140
}
141+
142+
// MARK: - Xet Client
143+
144+
#if canImport(Xet)
145+
/// Creates a Xet client for faster downloads.
146+
///
147+
/// The client is initialized with the bearer token if available.
148+
///
149+
/// - Returns: A Xet client instance.
150+
internal func createXetClient() async throws -> XetClient {
151+
if let token = try? await httpClient.tokenProvider.getToken() {
152+
return try XetClient.withToken(token: token)
153+
} else {
154+
return try XetClient()
155+
}
156+
}
157+
#endif
137158
}

0 commit comments

Comments
 (0)