tensorfs 0.3.26__tar.gz → 0.3.28__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tensorfs-0.3.26 → tensorfs-0.3.28}/Cargo.lock +2 -2
- {tensorfs-0.3.26 → tensorfs-0.3.28}/Cargo.toml +1 -1
- {tensorfs-0.3.26 → tensorfs-0.3.28}/PKG-INFO +1 -1
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/providers.rs +475 -1
- tensorfs-0.3.28/crates/tensorfs-core/tests/instrument_profiles.rs +213 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-py/src/store.rs +24 -3
- {tensorfs-0.3.26 → tensorfs-0.3.28}/pyproject.toml +1 -1
- {tensorfs-0.3.26 → tensorfs-0.3.28}/python/tensorfs/_ext.pyi +1 -1
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/pull.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/transport/tests.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.26 → tensorfs-0.3.28}/python/tensorfs/py.typed +0 -0
|
@@ -392,7 +392,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
392
392
|
|
|
393
393
|
[[package]]
|
|
394
394
|
name = "tensorfs-core"
|
|
395
|
-
version = "0.3.
|
|
395
|
+
version = "0.3.28"
|
|
396
396
|
dependencies = [
|
|
397
397
|
"fs2",
|
|
398
398
|
"minicbor",
|
|
@@ -404,7 +404,7 @@ dependencies = [
|
|
|
404
404
|
|
|
405
405
|
[[package]]
|
|
406
406
|
name = "tensorfs-py"
|
|
407
|
-
version = "0.3.
|
|
407
|
+
version = "0.3.28"
|
|
408
408
|
dependencies = [
|
|
409
409
|
"pyo3",
|
|
410
410
|
"tensorfs-core",
|
|
@@ -30,6 +30,8 @@ const API_PAGE_MAX: usize = 64;
|
|
|
30
30
|
/// A member the provider does not identify by digest is fetched whole and hashed; the
|
|
31
31
|
/// class is HuggingFace non-LFS files (configs, tokenizers), which git itself keeps small.
|
|
32
32
|
const UNPINNED_MEMBER_MAX_BYTES: u64 = 64 << 20;
|
|
33
|
+
/// An explicit ordinary-file selection is a bounded working set, not a weight download.
|
|
34
|
+
const FILE_SELECTION_MAX_BYTES: u64 = 64 << 20;
|
|
33
35
|
const MEMBER_MAX: usize = 4096;
|
|
34
36
|
/// A `*.safetensors.index.json` is a map from tensor name to shard file. It is JSON that
|
|
35
37
|
/// git itself keeps small, and the Go resolver bounded it at the same 32 MiB.
|
|
@@ -250,6 +252,17 @@ fn list_observed(
|
|
|
250
252
|
credentials: &dyn CredentialProvider,
|
|
251
253
|
deadline: Deadline,
|
|
252
254
|
ledger: &Ledger,
|
|
255
|
+
) -> Result<Vec<SourceMember>> {
|
|
256
|
+
list_selected_observed(uri, endpoints, credentials, deadline, ledger, None)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fn list_selected_observed(
|
|
260
|
+
uri: &SourceUri,
|
|
261
|
+
endpoints: &Endpoints,
|
|
262
|
+
credentials: &dyn CredentialProvider,
|
|
263
|
+
deadline: Deadline,
|
|
264
|
+
ledger: &Ledger,
|
|
265
|
+
files: Option<&[String]>,
|
|
253
266
|
) -> Result<Vec<SourceMember>> {
|
|
254
267
|
let policy = endpoints.policy(uri);
|
|
255
268
|
let base = endpoints.base(uri);
|
|
@@ -287,6 +300,9 @@ fn list_observed(
|
|
|
287
300
|
let Some(path) = json_str(row, "path") else {
|
|
288
301
|
return refuse(Code::MISSING_FIELD, "a tree row names no path");
|
|
289
302
|
};
|
|
303
|
+
if files.is_some_and(|files| !files.iter().any(|file| file == path)) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
290
306
|
if !member_path(path) {
|
|
291
307
|
// Every HuggingFace repository carries `.gitattributes`, and a
|
|
292
308
|
// leading dot is outside the member grammar. Refusing the whole
|
|
@@ -345,7 +361,10 @@ fn list_observed(
|
|
|
345
361
|
member: path.to_string(),
|
|
346
362
|
length,
|
|
347
363
|
sha256,
|
|
348
|
-
url: format!(
|
|
364
|
+
url: format!(
|
|
365
|
+
"{base}/{org}/{repo}/resolve/{revision}/{}",
|
|
366
|
+
path.replace(' ', "%20")
|
|
367
|
+
),
|
|
349
368
|
primary: false,
|
|
350
369
|
});
|
|
351
370
|
}
|
|
@@ -1342,6 +1361,145 @@ pub fn resolve(
|
|
|
1342
1361
|
})
|
|
1343
1362
|
}
|
|
1344
1363
|
|
|
1364
|
+
/// Resolve only explicitly named ordinary files at one immutable HF revision.
|
|
1365
|
+
/// No suffix has special meaning here: selecting Python bytes never executes them,
|
|
1366
|
+
/// and selecting an index does not expand it into tensor shards. Tensor carriers
|
|
1367
|
+
/// continue to use `resolve` and reviewed profile/carrier selection.
|
|
1368
|
+
pub fn resolve_files(
|
|
1369
|
+
uri: &SourceUri,
|
|
1370
|
+
files: &[String],
|
|
1371
|
+
endpoints: &Endpoints,
|
|
1372
|
+
credentials: &dyn CredentialProvider,
|
|
1373
|
+
deadline: Deadline,
|
|
1374
|
+
) -> Result<Resolution> {
|
|
1375
|
+
let SourceUri::HuggingFace {
|
|
1376
|
+
org,
|
|
1377
|
+
repo,
|
|
1378
|
+
revision,
|
|
1379
|
+
} = uri
|
|
1380
|
+
else {
|
|
1381
|
+
return refuse(
|
|
1382
|
+
Code::SOURCE_NOT_ALLOWED,
|
|
1383
|
+
"ordinary file selection requires HuggingFace",
|
|
1384
|
+
);
|
|
1385
|
+
};
|
|
1386
|
+
if files.is_empty() || files.len() > MEMBER_MAX {
|
|
1387
|
+
return refuse(
|
|
1388
|
+
Code::COUNT_CAP,
|
|
1389
|
+
"ordinary file selection count is outside the bound",
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
let mut names = files.to_vec();
|
|
1393
|
+
names.sort();
|
|
1394
|
+
if names
|
|
1395
|
+
.iter()
|
|
1396
|
+
.any(|name| !member_path(name) || crate::manifest::check_path(name).is_err())
|
|
1397
|
+
{
|
|
1398
|
+
return refuse(
|
|
1399
|
+
Code::PATH_ILLEGAL,
|
|
1400
|
+
"ordinary file selection requires exact member paths",
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
if names.windows(2).any(|pair| pair[0] == pair[1]) {
|
|
1404
|
+
return refuse(
|
|
1405
|
+
Code::DUPLICATE_KEY,
|
|
1406
|
+
"ordinary file selection names one member twice",
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
let ledger = Ledger::new();
|
|
1410
|
+
let policy = endpoints.policy(uri);
|
|
1411
|
+
let listed =
|
|
1412
|
+
list_selected_observed(uri, endpoints, credentials, deadline, &ledger, Some(&names))?;
|
|
1413
|
+
// Establish the whole roster before fetching any body. A missing later member
|
|
1414
|
+
// must not turn a partial requested tree into a successful smaller selection.
|
|
1415
|
+
let selected = names
|
|
1416
|
+
.iter()
|
|
1417
|
+
.map(|name| {
|
|
1418
|
+
listed
|
|
1419
|
+
.iter()
|
|
1420
|
+
.find(|member| &member.member == name)
|
|
1421
|
+
.ok_or_else(|| Refusal {
|
|
1422
|
+
code: Code::MISSING_FIELD,
|
|
1423
|
+
detail: format!("the source does not contain requested file {name:?}"),
|
|
1424
|
+
})
|
|
1425
|
+
})
|
|
1426
|
+
.collect::<Result<Vec<_>>>()?;
|
|
1427
|
+
let listed_bytes = selected.iter().try_fold(0u64, |total, member| {
|
|
1428
|
+
total.checked_add(member.length.unwrap_or(0))
|
|
1429
|
+
});
|
|
1430
|
+
if listed_bytes.is_none_or(|bytes| bytes > FILE_SELECTION_MAX_BYTES) {
|
|
1431
|
+
return refuse(
|
|
1432
|
+
Code::SIZE_CAP,
|
|
1433
|
+
"ordinary file selection exceeds its byte bound",
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
let mut remaining = FILE_SELECTION_MAX_BYTES;
|
|
1437
|
+
let mut resolved = Vec::with_capacity(selected.len());
|
|
1438
|
+
for member in selected {
|
|
1439
|
+
if member
|
|
1440
|
+
.length
|
|
1441
|
+
.map_or(remaining == 0, |length| length > remaining)
|
|
1442
|
+
{
|
|
1443
|
+
return refuse(
|
|
1444
|
+
Code::SIZE_CAP,
|
|
1445
|
+
"ordinary file selection exceeds its byte bound",
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
let (object, provenance) = if let Some(sha256) = &member.sha256 {
|
|
1449
|
+
let length = match member.length {
|
|
1450
|
+
Some(length) => length,
|
|
1451
|
+
None => probe_length(&member.url, &policy, credentials, deadline, &ledger)?,
|
|
1452
|
+
};
|
|
1453
|
+
(
|
|
1454
|
+
ObjectRef {
|
|
1455
|
+
sha256: sha256.clone(),
|
|
1456
|
+
length,
|
|
1457
|
+
},
|
|
1458
|
+
Provenance::Declared,
|
|
1459
|
+
)
|
|
1460
|
+
} else {
|
|
1461
|
+
let (body, _) = api_get(
|
|
1462
|
+
&member.url,
|
|
1463
|
+
&policy,
|
|
1464
|
+
credentials,
|
|
1465
|
+
remaining,
|
|
1466
|
+
deadline,
|
|
1467
|
+
&ledger,
|
|
1468
|
+
)?;
|
|
1469
|
+
if member
|
|
1470
|
+
.length
|
|
1471
|
+
.is_some_and(|length| length != body.len() as u64)
|
|
1472
|
+
{
|
|
1473
|
+
return refuse(
|
|
1474
|
+
Code::LENGTH_MISMATCH,
|
|
1475
|
+
"ordinary file body disagrees with listed length",
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
(ObjectRef::of(&body), Provenance::Computed)
|
|
1479
|
+
};
|
|
1480
|
+
remaining = remaining
|
|
1481
|
+
.checked_sub(object.length)
|
|
1482
|
+
.ok_or_else(|| Refusal {
|
|
1483
|
+
code: Code::SIZE_CAP,
|
|
1484
|
+
detail: "ordinary file selection exceeds its byte bound".into(),
|
|
1485
|
+
})?;
|
|
1486
|
+
resolved.push(ResolvedMember {
|
|
1487
|
+
member: member.member.clone(),
|
|
1488
|
+
object,
|
|
1489
|
+
url: member.url.clone(),
|
|
1490
|
+
provenance,
|
|
1491
|
+
carrier: false,
|
|
1492
|
+
requires: Vec::new(),
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
let canonical = format!("hf://{org}/{repo}@{revision}");
|
|
1496
|
+
Ok(Resolution {
|
|
1497
|
+
selection_sha256: selection_digest(&canonical, &resolved),
|
|
1498
|
+
canonical,
|
|
1499
|
+
members: resolved,
|
|
1500
|
+
})
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1345
1503
|
#[cfg(test)]
|
|
1346
1504
|
mod tests {
|
|
1347
1505
|
use super::*;
|
|
@@ -2231,4 +2389,320 @@ mod tests {
|
|
|
2231
2389
|
handle.join().unwrap();
|
|
2232
2390
|
let _ = std::fs::remove_dir_all(root);
|
|
2233
2391
|
}
|
|
2392
|
+
|
|
2393
|
+
#[test]
|
|
2394
|
+
fn explicit_files_resolve_only_named_bytes_and_retain_across_restart() {
|
|
2395
|
+
let config = b"{\"model_type\":\"test\"}".to_vec();
|
|
2396
|
+
let tokenizer = b"these are tokenizer bytes".to_vec();
|
|
2397
|
+
let expected = vec![ObjectRef::of(&config), ObjectRef::of(&tokenizer)];
|
|
2398
|
+
let listing = format!(
|
|
2399
|
+
r#"[
|
|
2400
|
+
{{"type":"file","path":"model.safetensors","size":100000000000,"lfs":{{"oid":"{}","size":100000000000}}}},
|
|
2401
|
+
{{"type":"file","path":"config.json","size":{}}},
|
|
2402
|
+
{{"type":"file","path":"tokenizer files/tokenizer.json","size":{}}},
|
|
2403
|
+
{{"type":"file","path":"unused.py","size":99}}
|
|
2404
|
+
]"#,
|
|
2405
|
+
"*".repeat(64),
|
|
2406
|
+
config.len(),
|
|
2407
|
+
tokenizer.len()
|
|
2408
|
+
);
|
|
2409
|
+
// Two order-independent resolutions each read list + two small bodies;
|
|
2410
|
+
// materialization reads the bodies once, and restart/warm admission reads none.
|
|
2411
|
+
let (base, seen, handle) = origin(8, move |request| {
|
|
2412
|
+
if request.path.contains("/tree/") {
|
|
2413
|
+
ok_body(listing.as_bytes(), "application/json")
|
|
2414
|
+
} else if request.path.ends_with("/config.json") {
|
|
2415
|
+
ok_body(&config, "application/json")
|
|
2416
|
+
} else if request.path.ends_with("/tokenizer.json") {
|
|
2417
|
+
ok_body(&tokenizer, "application/json")
|
|
2418
|
+
} else {
|
|
2419
|
+
panic!("unselected file requested: {}", request.path);
|
|
2420
|
+
}
|
|
2421
|
+
});
|
|
2422
|
+
let uri = SourceUri::parse(&format!("hf://org/repo@{}", "ab".repeat(20))).unwrap();
|
|
2423
|
+
let endpoints = Endpoints {
|
|
2424
|
+
huggingface: base,
|
|
2425
|
+
allow_local: true,
|
|
2426
|
+
..Endpoints::default()
|
|
2427
|
+
};
|
|
2428
|
+
let mut names = vec![
|
|
2429
|
+
"config.json".into(),
|
|
2430
|
+
"tokenizer files/tokenizer.json".into(),
|
|
2431
|
+
];
|
|
2432
|
+
let selected =
|
|
2433
|
+
resolve_files(&uri, &names, &endpoints, &Anonymous, Deadline::none()).unwrap();
|
|
2434
|
+
names.reverse();
|
|
2435
|
+
assert_eq!(
|
|
2436
|
+
selected,
|
|
2437
|
+
resolve_files(&uri, &names, &endpoints, &Anonymous, Deadline::none()).unwrap()
|
|
2438
|
+
);
|
|
2439
|
+
assert_eq!(
|
|
2440
|
+
selected
|
|
2441
|
+
.members
|
|
2442
|
+
.iter()
|
|
2443
|
+
.map(|m| m.object.clone())
|
|
2444
|
+
.collect::<Vec<_>>(),
|
|
2445
|
+
expected
|
|
2446
|
+
);
|
|
2447
|
+
assert!(selected
|
|
2448
|
+
.members
|
|
2449
|
+
.iter()
|
|
2450
|
+
.all(|m| m.provenance == Provenance::Computed && !m.carrier && m.requires.is_empty()));
|
|
2451
|
+
let root = temporary("selected-files");
|
|
2452
|
+
let store = Store::init(&root).unwrap();
|
|
2453
|
+
let producer = ObjectRef::of(b"files-producer").id();
|
|
2454
|
+
let recipient = ObjectRef::of(b"files-recipient").id();
|
|
2455
|
+
let (cold, transfers) = materialize_selected(
|
|
2456
|
+
&store,
|
|
2457
|
+
&producer,
|
|
2458
|
+
&selected,
|
|
2459
|
+
&endpoints.policy(&uri),
|
|
2460
|
+
&Anonymous,
|
|
2461
|
+
Deadline::none(),
|
|
2462
|
+
&mut |_, _| {},
|
|
2463
|
+
)
|
|
2464
|
+
.unwrap();
|
|
2465
|
+
assert_eq!(
|
|
2466
|
+
transfers.iter().map(|row| row.transferred).sum::<u64>(),
|
|
2467
|
+
selected.member_bytes()
|
|
2468
|
+
);
|
|
2469
|
+
crate::source_artifact::retain(&store, &producer, &recipient).unwrap();
|
|
2470
|
+
drop(store);
|
|
2471
|
+
let store = Store::open(&root).unwrap();
|
|
2472
|
+
let (warm, transfers) = materialize_selected(
|
|
2473
|
+
&store,
|
|
2474
|
+
&producer,
|
|
2475
|
+
&selected,
|
|
2476
|
+
&endpoints.policy(&uri),
|
|
2477
|
+
&Anonymous,
|
|
2478
|
+
Deadline::none(),
|
|
2479
|
+
&mut |_, _| {},
|
|
2480
|
+
)
|
|
2481
|
+
.unwrap();
|
|
2482
|
+
assert_eq!(warm, cold);
|
|
2483
|
+
assert!(transfers.iter().all(|row| row.held && row.transferred == 0));
|
|
2484
|
+
crate::source_artifact::release(&store, &producer).unwrap();
|
|
2485
|
+
crate::gc::collect(&root, false).unwrap();
|
|
2486
|
+
let retained = crate::source_artifact::read(&store, &recipient)
|
|
2487
|
+
.unwrap()
|
|
2488
|
+
.unwrap();
|
|
2489
|
+
assert!(retained.complete && !retained.released);
|
|
2490
|
+
for object in &expected {
|
|
2491
|
+
assert!(store.record_valid(&object.sha256).is_ok());
|
|
2492
|
+
}
|
|
2493
|
+
crate::source_artifact::release(&store, &recipient).unwrap();
|
|
2494
|
+
crate::gc::collect(&root, false).unwrap();
|
|
2495
|
+
for object in expected {
|
|
2496
|
+
assert!(!store.contains(&object.sha256));
|
|
2497
|
+
}
|
|
2498
|
+
handle.join().unwrap();
|
|
2499
|
+
assert_eq!(seen.lock().unwrap().len(), 8);
|
|
2500
|
+
assert!(seen
|
|
2501
|
+
.lock()
|
|
2502
|
+
.unwrap()
|
|
2503
|
+
.iter()
|
|
2504
|
+
.any(|(path, _)| path.contains("tokenizer%20files/")));
|
|
2505
|
+
std::fs::remove_dir_all(root).unwrap();
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
#[test]
|
|
2509
|
+
fn explicit_files_refuse_missing_and_over_budget_before_fetching_bodies() {
|
|
2510
|
+
for (names, listing, code) in [
|
|
2511
|
+
(
|
|
2512
|
+
vec!["absent.json".into(), "config.json".into()],
|
|
2513
|
+
r#"[{"type":"file","path":"config.json","size":2}]"#.to_string(),
|
|
2514
|
+
Code::MISSING_FIELD,
|
|
2515
|
+
),
|
|
2516
|
+
(
|
|
2517
|
+
vec!["config.json".into()],
|
|
2518
|
+
format!(
|
|
2519
|
+
r#"[{{"type":"file","path":"config.json","size":{}}}]"#,
|
|
2520
|
+
FILE_SELECTION_MAX_BYTES + 1
|
|
2521
|
+
),
|
|
2522
|
+
Code::SIZE_CAP,
|
|
2523
|
+
),
|
|
2524
|
+
] {
|
|
2525
|
+
let (base, _, handle) =
|
|
2526
|
+
origin(1, move |_| ok_body(listing.as_bytes(), "application/json"));
|
|
2527
|
+
let uri = SourceUri::parse(&format!("hf://org/repo@{}", "ab".repeat(20))).unwrap();
|
|
2528
|
+
let endpoints = Endpoints {
|
|
2529
|
+
huggingface: base,
|
|
2530
|
+
allow_local: true,
|
|
2531
|
+
..Endpoints::default()
|
|
2532
|
+
};
|
|
2533
|
+
assert_eq!(
|
|
2534
|
+
resolve_files(&uri, &names, &endpoints, &Anonymous, Deadline::none())
|
|
2535
|
+
.unwrap_err()
|
|
2536
|
+
.code,
|
|
2537
|
+
code
|
|
2538
|
+
);
|
|
2539
|
+
handle.join().unwrap();
|
|
2540
|
+
}
|
|
2541
|
+
let uri = SourceUri::parse(&format!("hf://org/repo@{}", "ab".repeat(20))).unwrap();
|
|
2542
|
+
for names in [
|
|
2543
|
+
vec!["../config.json".into()],
|
|
2544
|
+
vec!["*.json".into()],
|
|
2545
|
+
vec!["/config.json".into()],
|
|
2546
|
+
] {
|
|
2547
|
+
assert_eq!(
|
|
2548
|
+
resolve_files(
|
|
2549
|
+
&uri,
|
|
2550
|
+
&names,
|
|
2551
|
+
&Endpoints::default(),
|
|
2552
|
+
&Anonymous,
|
|
2553
|
+
Deadline::none()
|
|
2554
|
+
)
|
|
2555
|
+
.unwrap_err()
|
|
2556
|
+
.code,
|
|
2557
|
+
Code::PATH_ILLEGAL
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
assert_eq!(
|
|
2561
|
+
resolve_files(
|
|
2562
|
+
&uri,
|
|
2563
|
+
&["config.json".into(), "config.json".into()],
|
|
2564
|
+
&Endpoints::default(),
|
|
2565
|
+
&Anonymous,
|
|
2566
|
+
Deadline::none()
|
|
2567
|
+
)
|
|
2568
|
+
.unwrap_err()
|
|
2569
|
+
.code,
|
|
2570
|
+
Code::DUPLICATE_KEY
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
#[test]
|
|
2575
|
+
fn explicit_files_refuse_a_changed_listing_length() {
|
|
2576
|
+
let (base, _, handle) = origin(2, move |request| {
|
|
2577
|
+
if request.path.contains("/tree/") {
|
|
2578
|
+
ok_body(
|
|
2579
|
+
br#"[{"type":"file","path":"config.json","size":2}]"#,
|
|
2580
|
+
"application/json",
|
|
2581
|
+
)
|
|
2582
|
+
} else {
|
|
2583
|
+
ok_body(b"changed", "application/json")
|
|
2584
|
+
}
|
|
2585
|
+
});
|
|
2586
|
+
let uri = SourceUri::parse(&format!("hf://org/repo@{}", "ab".repeat(20))).unwrap();
|
|
2587
|
+
let endpoints = Endpoints {
|
|
2588
|
+
huggingface: base,
|
|
2589
|
+
allow_local: true,
|
|
2590
|
+
..Endpoints::default()
|
|
2591
|
+
};
|
|
2592
|
+
assert_eq!(
|
|
2593
|
+
resolve_files(
|
|
2594
|
+
&uri,
|
|
2595
|
+
&["config.json".into()],
|
|
2596
|
+
&endpoints,
|
|
2597
|
+
&Anonymous,
|
|
2598
|
+
Deadline::none()
|
|
2599
|
+
)
|
|
2600
|
+
.unwrap_err()
|
|
2601
|
+
.code,
|
|
2602
|
+
Code::LENGTH_MISMATCH
|
|
2603
|
+
);
|
|
2604
|
+
handle.join().unwrap();
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
#[test]
|
|
2608
|
+
fn explicit_files_changed_after_resolution_never_finish_a_native_root() {
|
|
2609
|
+
let replies = Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
|
2610
|
+
let (base, _, handle) = origin(3, move |request| {
|
|
2611
|
+
if request.path.contains("/tree/") {
|
|
2612
|
+
ok_body(
|
|
2613
|
+
br#"[{"type":"file","path":"config.json","size":2}]"#,
|
|
2614
|
+
"application/json",
|
|
2615
|
+
)
|
|
2616
|
+
} else {
|
|
2617
|
+
let attempt = replies.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
2618
|
+
ok_body(if attempt == 0 { b"AA" } else { b"BB" }, "application/json")
|
|
2619
|
+
}
|
|
2620
|
+
});
|
|
2621
|
+
let uri = SourceUri::parse(&format!("hf://org/repo@{}", "ab".repeat(20))).unwrap();
|
|
2622
|
+
let endpoints = Endpoints {
|
|
2623
|
+
huggingface: base,
|
|
2624
|
+
allow_local: true,
|
|
2625
|
+
..Endpoints::default()
|
|
2626
|
+
};
|
|
2627
|
+
let selected = resolve_files(
|
|
2628
|
+
&uri,
|
|
2629
|
+
&["config.json".into()],
|
|
2630
|
+
&endpoints,
|
|
2631
|
+
&Anonymous,
|
|
2632
|
+
Deadline::none(),
|
|
2633
|
+
)
|
|
2634
|
+
.unwrap();
|
|
2635
|
+
let root = temporary("changed-files");
|
|
2636
|
+
let store = Store::init(&root).unwrap();
|
|
2637
|
+
let producer = ObjectRef::of(b"changed-files-producer").id();
|
|
2638
|
+
let error = materialize_selected(
|
|
2639
|
+
&store,
|
|
2640
|
+
&producer,
|
|
2641
|
+
&selected,
|
|
2642
|
+
&endpoints.policy(&uri),
|
|
2643
|
+
&Anonymous,
|
|
2644
|
+
Deadline::none(),
|
|
2645
|
+
&mut |_, _| {},
|
|
2646
|
+
)
|
|
2647
|
+
.unwrap_err();
|
|
2648
|
+
assert_eq!(error.code, Code::OBJECT_ID_MISMATCH);
|
|
2649
|
+
assert!(!store.contains(&selected.members[0].object.sha256));
|
|
2650
|
+
assert!(
|
|
2651
|
+
!crate::source_artifact::read(&store, &producer)
|
|
2652
|
+
.unwrap()
|
|
2653
|
+
.unwrap()
|
|
2654
|
+
.complete
|
|
2655
|
+
);
|
|
2656
|
+
handle.join().unwrap();
|
|
2657
|
+
std::fs::remove_dir_all(root).unwrap();
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
#[test]
|
|
2661
|
+
fn explicit_files_can_retain_an_empty_file() {
|
|
2662
|
+
let (base, _, handle) = origin(2, move |request| {
|
|
2663
|
+
if request.path.contains("/tree/") {
|
|
2664
|
+
ok_body(
|
|
2665
|
+
br#"[{"type":"file","path":"empty.py","size":0}]"#,
|
|
2666
|
+
"application/json",
|
|
2667
|
+
)
|
|
2668
|
+
} else {
|
|
2669
|
+
ok_body(b"", "text/plain")
|
|
2670
|
+
}
|
|
2671
|
+
});
|
|
2672
|
+
let uri = SourceUri::parse(&format!("hf://org/repo@{}", "ab".repeat(20))).unwrap();
|
|
2673
|
+
let endpoints = Endpoints {
|
|
2674
|
+
huggingface: base,
|
|
2675
|
+
allow_local: true,
|
|
2676
|
+
..Endpoints::default()
|
|
2677
|
+
};
|
|
2678
|
+
let selected = resolve_files(
|
|
2679
|
+
&uri,
|
|
2680
|
+
&["empty.py".into()],
|
|
2681
|
+
&endpoints,
|
|
2682
|
+
&Anonymous,
|
|
2683
|
+
Deadline::none(),
|
|
2684
|
+
)
|
|
2685
|
+
.unwrap();
|
|
2686
|
+
assert_eq!(selected.members[0].object, ObjectRef::of(b""));
|
|
2687
|
+
let root = temporary("empty-files");
|
|
2688
|
+
let store = Store::init(&root).unwrap();
|
|
2689
|
+
let producer = ObjectRef::of(b"empty-files-producer").id();
|
|
2690
|
+
let (retained, transfers) = materialize_selected(
|
|
2691
|
+
&store,
|
|
2692
|
+
&producer,
|
|
2693
|
+
&selected,
|
|
2694
|
+
&endpoints.policy(&uri),
|
|
2695
|
+
&Anonymous,
|
|
2696
|
+
Deadline::none(),
|
|
2697
|
+
&mut |_, _| {},
|
|
2698
|
+
)
|
|
2699
|
+
.unwrap();
|
|
2700
|
+
assert!(retained.complete && !retained.released);
|
|
2701
|
+
assert_eq!(transfers[0].transferred, 0);
|
|
2702
|
+
assert!(store
|
|
2703
|
+
.record_valid(&selected.members[0].object.sha256)
|
|
2704
|
+
.is_ok());
|
|
2705
|
+
handle.join().unwrap();
|
|
2706
|
+
std::fs::remove_dir_all(root).unwrap();
|
|
2707
|
+
}
|
|
2234
2708
|
}
|