tensorfs 0.3.41__tar.gz → 0.3.43__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.41 → tensorfs-0.3.43}/Cargo.lock +3 -2
- {tensorfs-0.3.41 → tensorfs-0.3.43}/Cargo.toml +1 -1
- {tensorfs-0.3.41 → tensorfs-0.3.43}/PKG-INFO +1 -1
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/Cargo.toml +1 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/ingest.rs +29 -1
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/cbor.rs +20 -0
- tensorfs-0.3.43/crates/tensorfs-core/src/checkpoint_root.rs +314 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/gc.rs +8 -2
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/header.rs +8 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/convert.rs +40 -2
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/fingerprint.rs +5 -2
- tensorfs-0.3.43/crates/tensorfs-core/src/ingest/h3_lora.rs +267 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/mod.rs +6 -1
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/source.rs +19 -3
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/lib.rs +1 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/storage.rs +37 -11
- tensorfs-0.3.43/crates/tensorfs-core/tests/checkpoint_roots.rs +397 -0
- tensorfs-0.3.43/crates/tensorfs-core/tests/h3_lora.rs +451 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/source_plan.rs +3 -1
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/source_prefixes.rs +56 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/src/store.rs +76 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/pyproject.toml +1 -1
- {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/_derived_channel.py +1 -1
- {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/_ext.pyi +17 -0
- tensorfs-0.3.43/python/tensorfs/_source_channel.py +190 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/derived.py +24 -1
- tensorfs-0.3.41/python/tensorfs/_source_channel.py +0 -105
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/pull.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/tests.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/py.typed +0 -0
|
@@ -421,9 +421,10 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
421
421
|
|
|
422
422
|
[[package]]
|
|
423
423
|
name = "tensorfs-core"
|
|
424
|
-
version = "0.3.
|
|
424
|
+
version = "0.3.43"
|
|
425
425
|
dependencies = [
|
|
426
426
|
"fs2",
|
|
427
|
+
"libc",
|
|
427
428
|
"minicbor",
|
|
428
429
|
"rcgen",
|
|
429
430
|
"rusqlite",
|
|
@@ -434,7 +435,7 @@ dependencies = [
|
|
|
434
435
|
|
|
435
436
|
[[package]]
|
|
436
437
|
name = "tensorfs-py"
|
|
437
|
-
version = "0.3.
|
|
438
|
+
version = "0.3.43"
|
|
438
439
|
dependencies = [
|
|
439
440
|
"pyo3",
|
|
440
441
|
"tensorfs-core",
|
|
@@ -1506,6 +1506,27 @@ pub fn cmd_worker(root: &Path, target: &str, f: &Flags) -> ExitCode {
|
|
|
1506
1506
|
let build = tensorfs_core::ids::object_id(
|
|
1507
1507
|
format!("{}|{}", p.conv.name, env!("CARGO_PKG_VERSION")).as_bytes(),
|
|
1508
1508
|
);
|
|
1509
|
+
let source_bytes = match p.files.iter().try_fold(0u64, |total, source| {
|
|
1510
|
+
total.checked_add(source.header.file_len)
|
|
1511
|
+
}) {
|
|
1512
|
+
Some(bytes) => bytes,
|
|
1513
|
+
None => {
|
|
1514
|
+
return bail(tensorfs_core::err::Refusal {
|
|
1515
|
+
code: Code::ARITH_OVERFLOW,
|
|
1516
|
+
detail: "conversion input byte count overflow".into(),
|
|
1517
|
+
})
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
let logical_output_bytes = ok!(p.plan.declared_bytes());
|
|
1521
|
+
let logical_output_observation = match i64::try_from(logical_output_bytes) {
|
|
1522
|
+
Ok(bytes) => bytes,
|
|
1523
|
+
Err(_) => {
|
|
1524
|
+
return bail(tensorfs_core::err::Refusal {
|
|
1525
|
+
code: Code::ARITH_OVERFLOW,
|
|
1526
|
+
detail: "conversion output exceeds signed accounting extent".into(),
|
|
1527
|
+
})
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1509
1530
|
let profile = IngestProfile {
|
|
1510
1531
|
class: "normal".to_string(),
|
|
1511
1532
|
source_dialect: p.conv.dialect.to_string(),
|
|
@@ -1518,7 +1539,10 @@ pub fn cmd_worker(root: &Path, target: &str, f: &Flags) -> ExitCode {
|
|
|
1518
1539
|
v.dedup_by(|a, b| a.sha256 == b.sha256);
|
|
1519
1540
|
v
|
|
1520
1541
|
},
|
|
1521
|
-
|
|
1542
|
+
// The receipt quota counts materialized bytes. A reviewed permutation can
|
|
1543
|
+
// duplicate a low-rank factor into several canonical projections; its exact
|
|
1544
|
+
// planned output, not only the carrier length, must fit the same bound.
|
|
1545
|
+
max_source_bytes: source_bytes.max(logical_output_bytes),
|
|
1522
1546
|
max_tensors: limits::MAX_TENSORS as u64,
|
|
1523
1547
|
};
|
|
1524
1548
|
ok!(profile.validate(®istry::platform_digests()));
|
|
@@ -1547,6 +1571,10 @@ pub fn cmd_worker(root: &Path, target: &str, f: &Flags) -> ExitCode {
|
|
|
1547
1571
|
let isolation_tier: u64 = flag_num(f, "isolation-tier", 1);
|
|
1548
1572
|
out.obs
|
|
1549
1573
|
.push(("source_bytes".to_string(), profile.max_source_bytes as i64));
|
|
1574
|
+
out.obs.push((
|
|
1575
|
+
"logical_output_bytes".to_string(),
|
|
1576
|
+
logical_output_observation,
|
|
1577
|
+
));
|
|
1550
1578
|
out.obs.push(("golden_cases".to_string(), golden as i64));
|
|
1551
1579
|
out.obs
|
|
1552
1580
|
.push(("isolation_tier".to_string(), isolation_tier as i64));
|
|
@@ -436,6 +436,26 @@ fn encode_part(e: &mut Encoder<Bounded>, role: &str, part: &Part) -> Result<()>
|
|
|
436
436
|
Ok(())
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
pub(crate) fn encoded_tensor_identity(tensor: &Tensor) -> Result<String> {
|
|
440
|
+
let mut encoder = Encoder::new(Bounded { bytes: Vec::new() });
|
|
441
|
+
emit("tensor identity", encoder.array(5))?;
|
|
442
|
+
emit(
|
|
443
|
+
"tensor identity domain",
|
|
444
|
+
encoder.str("tensorfs.encoded-tensor/1"),
|
|
445
|
+
)?;
|
|
446
|
+
emit("tensor identity dtype", encoder.u8(tensor.dtype.code()))?;
|
|
447
|
+
encode_shape(&mut encoder, &tensor.shape)?;
|
|
448
|
+
emit("tensor identity encoding", encoder.str(&tensor.encoding))?;
|
|
449
|
+
emit(
|
|
450
|
+
"tensor identity parts",
|
|
451
|
+
encoder.array(tensor.parts.len() as u64),
|
|
452
|
+
)?;
|
|
453
|
+
for (role, part) in &tensor.parts {
|
|
454
|
+
encode_part(&mut encoder, role, part)?;
|
|
455
|
+
}
|
|
456
|
+
Ok(crate::ids::object_id(&encoder.into_writer().bytes))
|
|
457
|
+
}
|
|
458
|
+
|
|
439
459
|
fn decode_part(d: &mut Decoder<'_>, what: &str) -> Result<(String, Part)> {
|
|
440
460
|
exact_array(d, what, 4)?;
|
|
441
461
|
let role = text(d, &format!("{what}.role"), limits::MAX_NAME_BYTES)?;
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
//! Independent input custody for an exact, already-materialized repository checkpoint.
|
|
2
|
+
//! These filesystem roots retain bytes; they do not claim a computation or issue a receipt.
|
|
3
|
+
|
|
4
|
+
use std::fs::{self, File, OpenOptions};
|
|
5
|
+
use std::io::{Read, Write};
|
|
6
|
+
use std::os::unix::fs::OpenOptionsExt;
|
|
7
|
+
use std::path::PathBuf;
|
|
8
|
+
|
|
9
|
+
use fs2::FileExt;
|
|
10
|
+
|
|
11
|
+
use crate::canon::{Fields, Value};
|
|
12
|
+
use crate::catalog::WriterGuard;
|
|
13
|
+
use crate::err::{refuse, Code, Refusal, Result};
|
|
14
|
+
use crate::ids::{hex64, prefixed, Doc, ObjectRef, Plain};
|
|
15
|
+
use crate::repository::{Repository, RepositoryName};
|
|
16
|
+
use crate::storage::HeldKey;
|
|
17
|
+
use crate::store::{Store, O_NOFOLLOW};
|
|
18
|
+
|
|
19
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
20
|
+
pub struct CheckpointRoot {
|
|
21
|
+
pub owner: String,
|
|
22
|
+
pub repository: RepositoryName,
|
|
23
|
+
pub manifest: ObjectRef,
|
|
24
|
+
pub released: bool,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl Plain for CheckpointRoot {
|
|
28
|
+
const MAX_BYTES: usize = 4096;
|
|
29
|
+
|
|
30
|
+
fn from_value(value: &Value) -> Result<Self> {
|
|
31
|
+
let mut fields = Fields::new("CheckpointRoot", value)?;
|
|
32
|
+
let owner = prefixed("checkpoint root owner", fields.req_str("owner")?)?;
|
|
33
|
+
let repository = repository(fields.req_str("repository")?)?;
|
|
34
|
+
let manifest = ObjectRef::from_value("checkpoint root manifest", fields.req("manifest")?)?;
|
|
35
|
+
let released = match fields.req("released")? {
|
|
36
|
+
Value::Bool(value) => *value,
|
|
37
|
+
_ => return refuse(Code::WRONG_TYPE, "checkpoint root release must be boolean"),
|
|
38
|
+
};
|
|
39
|
+
fields.done()?;
|
|
40
|
+
if manifest.length == 0 || manifest.length > crate::limits::INT_MAX as u64 {
|
|
41
|
+
return refuse(Code::LENGTH_MISMATCH, "checkpoint root manifest is empty");
|
|
42
|
+
}
|
|
43
|
+
Ok(Self {
|
|
44
|
+
owner,
|
|
45
|
+
repository,
|
|
46
|
+
manifest,
|
|
47
|
+
released,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn to_value(&self) -> Value {
|
|
52
|
+
Value::obj(vec![
|
|
53
|
+
("owner", Value::str(self.owner.clone())),
|
|
54
|
+
(
|
|
55
|
+
"repository",
|
|
56
|
+
Value::str(format!("{}/{}", self.repository.org, self.repository.name)),
|
|
57
|
+
),
|
|
58
|
+
("manifest", self.manifest.to_value()),
|
|
59
|
+
("released", Value::Bool(self.released)),
|
|
60
|
+
])
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub fn repository(value: &str) -> Result<RepositoryName> {
|
|
65
|
+
let (org, name) = value.split_once('/').ok_or_else(|| Refusal {
|
|
66
|
+
code: Code::KEY_GRAMMAR,
|
|
67
|
+
detail: "checkpoint source repository must be org/name".into(),
|
|
68
|
+
})?;
|
|
69
|
+
RepositoryName::new(org, name)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn io(error: std::io::Error) -> Refusal {
|
|
73
|
+
Refusal {
|
|
74
|
+
code: Code::IO_FAILED,
|
|
75
|
+
detail: format!("checkpoint root: {error}"),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fn directory(store: &Store) -> PathBuf {
|
|
80
|
+
store.root().join("roots/checkpoints")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fn path(store: &Store, owner: &str) -> Result<PathBuf> {
|
|
84
|
+
prefixed("checkpoint root owner", owner)?;
|
|
85
|
+
Ok(directory(store).join(format!("{}.json", &owner[7..])))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Inspect the native root, including its permanent release tombstone.
|
|
89
|
+
pub fn read(store: &Store, owner: &str) -> Result<Option<CheckpointRoot>> {
|
|
90
|
+
let file = match OpenOptions::new()
|
|
91
|
+
.read(true)
|
|
92
|
+
.custom_flags(O_NOFOLLOW)
|
|
93
|
+
.open(path(store, owner)?)
|
|
94
|
+
{
|
|
95
|
+
Ok(file) => file,
|
|
96
|
+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
|
97
|
+
Err(error) => return Err(io(error)),
|
|
98
|
+
};
|
|
99
|
+
let mut bytes = Vec::new();
|
|
100
|
+
file.take(CheckpointRoot::MAX_BYTES as u64 + 1)
|
|
101
|
+
.read_to_end(&mut bytes)
|
|
102
|
+
.map_err(io)?;
|
|
103
|
+
let root = CheckpointRoot::parse(&bytes)?;
|
|
104
|
+
if root.owner != owner {
|
|
105
|
+
return refuse(
|
|
106
|
+
Code::CROSS_SUBJECT_REPLAY,
|
|
107
|
+
"checkpoint root path names another owner",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
Ok(Some(root))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn lock(store: &Store, owner: &str) -> Result<File> {
|
|
114
|
+
let path = path(store, owner)?.with_extension("lock");
|
|
115
|
+
fs::create_dir_all(directory(store)).map_err(io)?;
|
|
116
|
+
let file = OpenOptions::new()
|
|
117
|
+
.read(true)
|
|
118
|
+
.write(true)
|
|
119
|
+
.create(true)
|
|
120
|
+
.truncate(false)
|
|
121
|
+
.custom_flags(O_NOFOLLOW)
|
|
122
|
+
.open(path)
|
|
123
|
+
.map_err(io)?;
|
|
124
|
+
file.lock_exclusive().map_err(io)?;
|
|
125
|
+
Ok(file)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fn write(store: &Store, root: &CheckpointRoot) -> Result<()> {
|
|
129
|
+
let path = path(store, &root.owner)?;
|
|
130
|
+
let temporary = path.with_extension(format!("tmp-{}", crate::meta::now_nanos_unique()));
|
|
131
|
+
let result = (|| {
|
|
132
|
+
let mut file = OpenOptions::new()
|
|
133
|
+
.write(true)
|
|
134
|
+
.create_new(true)
|
|
135
|
+
.open(&temporary)
|
|
136
|
+
.map_err(io)?;
|
|
137
|
+
file.write_all(&root.canonical_bytes())
|
|
138
|
+
.and_then(|()| file.sync_all())
|
|
139
|
+
.map_err(io)?;
|
|
140
|
+
fs::rename(&temporary, path).map_err(io)?;
|
|
141
|
+
for directory in [
|
|
142
|
+
directory(store),
|
|
143
|
+
store.root().join("roots"),
|
|
144
|
+
store.root().to_path_buf(),
|
|
145
|
+
] {
|
|
146
|
+
File::open(directory)
|
|
147
|
+
.and_then(|file| file.sync_all())
|
|
148
|
+
.map_err(io)?;
|
|
149
|
+
}
|
|
150
|
+
Ok(())
|
|
151
|
+
})();
|
|
152
|
+
let _ = fs::remove_file(temporary);
|
|
153
|
+
result
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn identity(owner: &str, source: &str, manifest: ObjectRef) -> Result<CheckpointRoot> {
|
|
157
|
+
hex64("checkpoint root manifest", &manifest.sha256)?;
|
|
158
|
+
if manifest.length == 0 || manifest.length > crate::limits::INT_MAX as u64 {
|
|
159
|
+
return refuse(Code::LENGTH_MISMATCH, "checkpoint root manifest is empty");
|
|
160
|
+
}
|
|
161
|
+
Ok(CheckpointRoot {
|
|
162
|
+
owner: prefixed("checkpoint root owner", owner)?,
|
|
163
|
+
repository: repository(source)?,
|
|
164
|
+
manifest,
|
|
165
|
+
released: false,
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn same_subject(root: &CheckpointRoot, wanted: &CheckpointRoot) -> Result<()> {
|
|
170
|
+
if root.repository != wanted.repository || root.manifest != wanted.manifest {
|
|
171
|
+
return refuse(
|
|
172
|
+
Code::CROSS_SUBJECT_REPLAY,
|
|
173
|
+
"checkpoint root already names another subject",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
Ok(())
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn verify(store: &Store, manifest: &ObjectRef) -> Result<()> {
|
|
180
|
+
let model = store.read_manifest(manifest)?;
|
|
181
|
+
let closure = crate::checkpoint::walk_cozytensors(store, &model)?;
|
|
182
|
+
closure.require_resident(store)?;
|
|
183
|
+
for object in closure.distinct() {
|
|
184
|
+
let record = store.record_valid(&object.sha256).map_err(|why| Refusal {
|
|
185
|
+
code: Code::OBJECT_CORRUPT,
|
|
186
|
+
detail: format!("checkpoint root closure has no current verification record: {why}"),
|
|
187
|
+
})?;
|
|
188
|
+
if record.length != object.length {
|
|
189
|
+
return refuse(
|
|
190
|
+
Code::LENGTH_MISMATCH,
|
|
191
|
+
"checkpoint root closure record changed length",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
Ok(())
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Retain the runtime closure of an exact native repository checkpoint.
|
|
199
|
+
pub fn retain(
|
|
200
|
+
store: &Store,
|
|
201
|
+
owner: &str,
|
|
202
|
+
source: &str,
|
|
203
|
+
manifest: ObjectRef,
|
|
204
|
+
) -> Result<CheckpointRoot> {
|
|
205
|
+
let wanted = identity(owner, source, manifest)?;
|
|
206
|
+
let _gc = WriterGuard::acquire(store.root())?;
|
|
207
|
+
let _owner = lock(store, owner)?;
|
|
208
|
+
if let Some(root) = read(store, owner)? {
|
|
209
|
+
same_subject(&root, &wanted)?;
|
|
210
|
+
if root.released {
|
|
211
|
+
return refuse(
|
|
212
|
+
Code::TRANSACTION_CLOSED,
|
|
213
|
+
"checkpoint root was permanently released",
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
verify(store, &root.manifest)?;
|
|
217
|
+
return Ok(root);
|
|
218
|
+
}
|
|
219
|
+
verify_source(store, &wanted.repository, &wanted.manifest)?;
|
|
220
|
+
write(store, &wanted)?;
|
|
221
|
+
Ok(wanted)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// Validate all source inputs before the execution owner creates any recipient roots.
|
|
225
|
+
pub fn check_source(store: &Store, source: &str, manifest: &ObjectRef) -> Result<()> {
|
|
226
|
+
let repository = repository(source)?;
|
|
227
|
+
let _gc = WriterGuard::acquire(store.root())?;
|
|
228
|
+
verify_source(store, &repository, manifest)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fn verify_source(store: &Store, repository: &RepositoryName, manifest: &ObjectRef) -> Result<()> {
|
|
232
|
+
let file = OpenOptions::new()
|
|
233
|
+
.read(true)
|
|
234
|
+
.custom_flags(O_NOFOLLOW)
|
|
235
|
+
.open(store.repository_path(repository))
|
|
236
|
+
.map_err(|error| {
|
|
237
|
+
if error.kind() == std::io::ErrorKind::NotFound {
|
|
238
|
+
Refusal {
|
|
239
|
+
code: Code::REPOSITORY_ABSENT,
|
|
240
|
+
detail: "checkpoint source repository is absent".into(),
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
io(error)
|
|
244
|
+
}
|
|
245
|
+
})?;
|
|
246
|
+
let mut body = Vec::new();
|
|
247
|
+
file.take(Repository::MAX_BYTES as u64 + 1)
|
|
248
|
+
.read_to_end(&mut body)
|
|
249
|
+
.map_err(io)?;
|
|
250
|
+
let actual = Repository::parse(&body)?;
|
|
251
|
+
if &actual.repo != repository
|
|
252
|
+
|| !actual
|
|
253
|
+
.checkpoints
|
|
254
|
+
.iter()
|
|
255
|
+
.any(|row| &row.manifest == manifest)
|
|
256
|
+
{
|
|
257
|
+
return refuse(
|
|
258
|
+
Code::ROOT_ABSENT,
|
|
259
|
+
"checkpoint has no exact source repository membership",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
verify(store, manifest)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/// Release this exact owner permanently, even before its first acquisition.
|
|
266
|
+
pub fn release(
|
|
267
|
+
store: &Store,
|
|
268
|
+
owner: &str,
|
|
269
|
+
source: &str,
|
|
270
|
+
manifest: ObjectRef,
|
|
271
|
+
) -> Result<CheckpointRoot> {
|
|
272
|
+
let mut wanted = identity(owner, source, manifest)?;
|
|
273
|
+
let _gc = WriterGuard::acquire(store.root())?;
|
|
274
|
+
let _owner = lock(store, owner)?;
|
|
275
|
+
if let Some(root) = read(store, owner)? {
|
|
276
|
+
same_subject(&root, &wanted)?;
|
|
277
|
+
if root.released {
|
|
278
|
+
return Ok(root);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
wanted.released = true;
|
|
282
|
+
write(store, &wanted)?;
|
|
283
|
+
Ok(wanted)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
pub(crate) fn held_objects(store: &Store) -> Result<Vec<HeldKey>> {
|
|
287
|
+
let entries = match fs::read_dir(directory(store)) {
|
|
288
|
+
Ok(entries) => entries,
|
|
289
|
+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
|
|
290
|
+
Err(error) => return Err(io(error)),
|
|
291
|
+
};
|
|
292
|
+
let mut objects = Vec::new();
|
|
293
|
+
for entry in entries {
|
|
294
|
+
let entry = entry.map_err(io)?;
|
|
295
|
+
let name = entry.file_name();
|
|
296
|
+
let name = name.to_string_lossy();
|
|
297
|
+
let Some(hex) = name.strip_suffix(".json") else {
|
|
298
|
+
continue;
|
|
299
|
+
};
|
|
300
|
+
let root = read(store, &format!("sha256:{hex}"))?.ok_or_else(|| Refusal {
|
|
301
|
+
code: Code::ROOT_ABSENT,
|
|
302
|
+
detail: "checkpoint root disappeared during census".into(),
|
|
303
|
+
})?;
|
|
304
|
+
if !root.released {
|
|
305
|
+
objects.push(HeldKey {
|
|
306
|
+
key: crate::storage::manifest_key(&root.manifest.sha256)?,
|
|
307
|
+
kind: "cozytensors".into(),
|
|
308
|
+
length: root.manifest.length,
|
|
309
|
+
sha256: root.manifest.sha256,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
Ok(objects)
|
|
314
|
+
}
|
|
@@ -134,7 +134,13 @@ pub fn session_holds(store: &Store) -> Result<(Vec<HeldKey>, Vec<String>)> {
|
|
|
134
134
|
.collect::<Result<Vec<_>>>()?;
|
|
135
135
|
holds.extend(crate::derived::retained_objects(store)?);
|
|
136
136
|
holds.extend(crate::source_artifact::held_objects(store)?);
|
|
137
|
-
holds.
|
|
137
|
+
holds.extend(crate::checkpoint_root::held_objects(store)?);
|
|
138
|
+
// A full snapshot obligation wins over a runtime-only pin of the same manifest.
|
|
139
|
+
holds.sort_by(|left, right| {
|
|
140
|
+
left.key
|
|
141
|
+
.cmp(&right.key)
|
|
142
|
+
.then_with(|| (left.kind == "cozytensors").cmp(&(right.kind == "cozytensors")))
|
|
143
|
+
});
|
|
138
144
|
if holds
|
|
139
145
|
.windows(2)
|
|
140
146
|
.any(|pair| pair[0].key == pair[1].key && pair[0].length != pair[1].length)
|
|
@@ -165,7 +171,7 @@ pub(crate) fn retained_manifests(store: &Store) -> Result<Vec<crate::ids::Object
|
|
|
165
171
|
roots.extend(
|
|
166
172
|
holds
|
|
167
173
|
.into_iter()
|
|
168
|
-
.filter(|hold| hold.
|
|
174
|
+
.filter(|hold| hold.is_manifest())
|
|
169
175
|
.map(|hold| crate::ids::ObjectRef {
|
|
170
176
|
sha256: hold.sha256,
|
|
171
177
|
length: hold.length,
|
|
@@ -93,6 +93,14 @@ pub struct Tensor {
|
|
|
93
93
|
pub parts: Vec<(String, Part)>,
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
impl Tensor {
|
|
97
|
+
/// Opaque equality of encoded geometry and bytes, independent of tensor names
|
|
98
|
+
/// and unrelated header configs or encodings. This is not a Store capability.
|
|
99
|
+
pub fn encoded_identity(&self) -> Result<String> {
|
|
100
|
+
crate::cbor::encoded_tensor_identity(self)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
96
104
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
97
105
|
pub struct Asset {
|
|
98
106
|
pub logical_sha256: String,
|
|
@@ -53,7 +53,7 @@ pub struct Converter {
|
|
|
53
53
|
pub validated_on_real_bytes: bool,
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
pub const CONVERTERS: [Converter;
|
|
56
|
+
pub const CONVERTERS: [Converter; 5] = [
|
|
57
57
|
Converter {
|
|
58
58
|
name: "diffusers.identity/1",
|
|
59
59
|
dialect: "safetensors.diffusers",
|
|
@@ -79,6 +79,13 @@ pub const CONVERTERS: [Converter; 4] = [
|
|
|
79
79
|
REFERENCE; nothing is re-hashed and nothing is re-uploaded.",
|
|
80
80
|
validated_on_real_bytes: true,
|
|
81
81
|
},
|
|
82
|
+
Converter {
|
|
83
|
+
name: "h3.lora/1",
|
|
84
|
+
dialect: "safetensors.h3_lora",
|
|
85
|
+
class: Class::Permutation,
|
|
86
|
+
doc: "Native H3 LoRA A/B and Kohya down/up factors: split head-interleaved QKV B rows, swap SwiGLU B halves, preserve A and alpha without arithmetic.",
|
|
87
|
+
validated_on_real_bytes: false,
|
|
88
|
+
},
|
|
82
89
|
Converter {
|
|
83
90
|
name: "h3.native/2",
|
|
84
91
|
dialect: "safetensors.h3_native",
|
|
@@ -376,7 +383,12 @@ impl Plan {
|
|
|
376
383
|
let mut n = 0u64;
|
|
377
384
|
for o in &self.ops {
|
|
378
385
|
for r in &o.roles {
|
|
379
|
-
n
|
|
386
|
+
n = n
|
|
387
|
+
.checked_add(checked_bytes(&o.out_key, &r.shape, r.dtype)?)
|
|
388
|
+
.ok_or_else(|| crate::err::Refusal {
|
|
389
|
+
code: Code::ARITH_OVERFLOW,
|
|
390
|
+
detail: "conversion output byte count overflow".into(),
|
|
391
|
+
})?;
|
|
380
392
|
}
|
|
381
393
|
}
|
|
382
394
|
Ok(n)
|
|
@@ -947,6 +959,15 @@ fn plan_carrier(
|
|
|
947
959
|
cands: &Candidates<'_>,
|
|
948
960
|
p: &mut Plan,
|
|
949
961
|
) -> Result<()> {
|
|
962
|
+
if conv.name == "h3.lora/1" {
|
|
963
|
+
if allowed != ["plain/1"] {
|
|
964
|
+
return refuse(
|
|
965
|
+
Code::UNKNOWN_ENCODING,
|
|
966
|
+
"H3 LoRA ingestion preserves floating point factors in plain/1",
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
return super::h3_lora::plan(component, file, h, cands.plain, p);
|
|
970
|
+
}
|
|
950
971
|
let alias = Allowed(allowed);
|
|
951
972
|
p.header_bytes += h.header_bytes;
|
|
952
973
|
let plain_map = role_map("plain/1")?;
|
|
@@ -1738,6 +1759,23 @@ pub fn h3_geometry(
|
|
|
1738
1759
|
/// error at ~90% numerically while nothing raised.
|
|
1739
1760
|
pub fn golden_cases(c: &Converter) -> Vec<(&'static str, Xform)> {
|
|
1740
1761
|
match c.name {
|
|
1762
|
+
"h3.lora/1" => vec![
|
|
1763
|
+
(
|
|
1764
|
+
"H3 LoRA SwiGLU B rows",
|
|
1765
|
+
Xform::SwapHalves {
|
|
1766
|
+
half_bytes: 14336 * 16 * 2,
|
|
1767
|
+
},
|
|
1768
|
+
),
|
|
1769
|
+
(
|
|
1770
|
+
"H3 LoRA QKV B rows",
|
|
1771
|
+
Xform::QkvSplit {
|
|
1772
|
+
groups: H3_HEADS,
|
|
1773
|
+
shares: [1, 1, 1],
|
|
1774
|
+
take: 0,
|
|
1775
|
+
unit_bytes: H3_HEAD_DIM * 16 * 2,
|
|
1776
|
+
},
|
|
1777
|
+
),
|
|
1778
|
+
],
|
|
1741
1779
|
"h3.native/2" => vec![
|
|
1742
1780
|
(
|
|
1743
1781
|
"h3-swiglu gate/value rows",
|
|
@@ -855,7 +855,9 @@ pub fn validate_source_member(member: &str) -> Result<()> {
|
|
|
855
855
|
}
|
|
856
856
|
|
|
857
857
|
pub fn validate_source_member_prefix(prefix: &str) -> Result<()> {
|
|
858
|
-
if !prefix.
|
|
858
|
+
if !prefix.is_empty()
|
|
859
|
+
&& (!prefix.ends_with('/') || validate_source_member(&format!("{prefix}member")).is_err())
|
|
860
|
+
{
|
|
859
861
|
return refuse(
|
|
860
862
|
Code::KEY_GRAMMAR,
|
|
861
863
|
format!("source member prefix {prefix:?} is not a safe canonical prefix"),
|
|
@@ -867,8 +869,9 @@ pub fn validate_source_member_prefix(prefix: &str) -> Result<()> {
|
|
|
867
869
|
/// Decoded `__metadata__` facts the border is willing to carry as EVIDENCE STAMPS. Foreign
|
|
868
870
|
/// JSON is decoded through this strict list or discarded — it never survives as itself.
|
|
869
871
|
pub fn evidence_stamps(h: &SourceHeader) -> Result<Vec<(String, String)>> {
|
|
870
|
-
const KEPT: [&str;
|
|
872
|
+
const KEPT: [&str; 5] = [
|
|
871
873
|
"modelspec.architecture",
|
|
874
|
+
"ss_h3_training_mode",
|
|
872
875
|
"modelspec.prediction_type",
|
|
873
876
|
"format",
|
|
874
877
|
"scaled_fp8",
|