Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions recipe/jemm/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
LABELS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
SYSTEM = ("Choose the best available candidate for the question using only the supplied state. "
"Return exactly one candidate label.")
CALIBRATION = {"temperature": 1.3480874159655591, "mm_temperature": 1.3954832341582943,
"threshold": 0.9872681877423998}
INVENTORY_PATH = Path(__file__).with_name("pinned_inventory.json")


Expand Down Expand Up @@ -189,9 +187,14 @@ def export_checkpoint(base, adapter, out, provenance, *, tokenizer=None, invento
pairs, scale = adapter_pairs(inventory["base"]["tensors"], inventory["adapter"]["tensors"], adapter_config)
if inventory.get("provenance") == PINS and (adapter_config["r"] != 16 or len(pairs) != 496 or scale != 2.):
raise ValueError("pinned JEMM requires rank16, 496 FP32 LoRA pairs and scale2")
calibration = json.loads((adapter / "decision_config.json").read_text())
if any(calibration.get(key) != value for key, value in CALIBRATION.items()):
raise ValueError("decision calibration differs from pinned JEMM")
decision = json.loads((adapter / "decision_config.json").read_text())
calibration = {}
for key in ("temperature", "mm_temperature", "threshold"):
value = decision.get(key) if isinstance(decision, dict) else None
if type(value) not in (int, float) or not math.isfinite(value) or (
value <= 0 if key != "threshold" else not 0 <= value <= 1):
raise ValueError(f"invalid decision calibration: {key}")
calibration[key] = value
if tokenizer is None:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(str(base), local_files_only=True)
Expand Down Expand Up @@ -310,7 +313,7 @@ def export_checkpoint(base, adapter, out, provenance, *, tokenizer=None, invento
raise ValueError("original FP32 adapter checksum mismatch")
if "jemm_lm_head.safetensors" not in exports or "vision.safetensors" not in exports:
raise ValueError("export lacks selected head or vision tensors")
manifest = {"format": "jemm-native/2", "model_id": "JEMM", **PINS, **CALIBRATION,
manifest = {"format": "jemm-native/2", "model_id": "JEMM", **PINS, **calibration,
"max_tokens": 8192, "max_mm_tokens": 3072, "chat_prefix": chat_prefix, "chat_suffix": chat_suffix,
"enable_thinking": False, "preserve_thinking": False, "labels": list(LABELS), "label_token_ids": label_ids,
"label_head_file": "jemm_lm_head.safetensors", "label_head_tensor": "weight", "label_head_dtype": "BF16",
Expand Down
9 changes: 5 additions & 4 deletions src/models/jemm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ rows from the untied LM head are extracted without merging adapter weights.
`jemm_export.json` records every exported file's SHA-256 and the adapter contract.
Startup verifies original shard and adapter pins before CUDA initialization;
legacy premerged `jemm-native/1` exports are explicitly rejected. Keep files immutable
while serving. This head requires the pinned temperatures 1.3480874159655591
(text), 1.3954832341582943 (images) and threshold 0.9872681877423998. Follow-up
#122 loads validated calibration from the checksum-covered decision config;
its calibration policy is a separate change. The threshold is provenance and
while serving. Calibration loads from checksum-covered `decision_config.json`;
temperatures must be finite and positive, threshold finite and within [0,1],
and all values must agree with the export manifest. The pinned artifact values
are 1.3480874159655591 (text), 1.3954832341582943 (images) and
0.9872681877423998 (threshold). The threshold is provenance and
does not gate SystemOne responses.

## Request and response contract
Expand Down
1 change: 1 addition & 0 deletions src/models/jemm/native/src/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub fn verify_export_files(dir: &Path, manifest: &Value) -> Result<()> {
"config.json",
"tokenizer.json",
"preprocessor_config.json",
"decision_config.json",
"model.safetensors.index.json",
"vision.safetensors",
"jemm_lm_head.safetensors",
Expand Down
3 changes: 0 additions & 3 deletions src/models/jemm/native/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ pub const LABELS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
pub const SYSTEM: &str = "Choose the best available candidate for the question using only the supplied state. Return exactly one candidate label.";
pub const BASE_REVISION: &str = "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0";
pub const CHECKPOINT_REVISION: &str = "76e3c209e8441fa658221c7ba2725bad2f811176";
pub const TEMPERATURE: f64 = 1.3480874159655591;
pub const MM_TEMPERATURE: f64 = 1.3954832341582943;
pub const THRESHOLD: f64 = 0.9872681877423998;
pub const SOURCE_REVISION: &str = "6822fe0fd53c5e6670af6ba99fb2c857a661e532";
#[derive(Debug)]
pub struct Question {
Expand Down
37 changes: 27 additions & 10 deletions src/models/jemm/native/src/processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,33 @@ impl Processor {
prefix.contains(contract::SYSTEM) && suffix.contains("<think>\n\n</think>"),
"expected JEMM system and disabled-thinking template"
);
let temperature = manifest["temperature"].as_f64().context("temperature")?;
let mm_temperature = manifest["mm_temperature"]
.as_f64()
.context("mm_temperature")?;
ensure!(
temperature == contract::TEMPERATURE
&& mm_temperature == contract::MM_TEMPERATURE
&& manifest["threshold"].as_f64() == Some(contract::THRESHOLD),
"expected pinned JEMM calibration"
);
let calibration: Value = serde_json::from_slice(
&std::fs::read(dir.join("decision_config.json")).context("decision_config.json")?,
)?;
let mut values = [0.0; 3];
for (index, key) in ["temperature", "mm_temperature", "threshold"]
.iter()
.enumerate()
{
let value = calibration[key]
.as_f64()
.with_context(|| format!("calibration {key}"))?;
ensure!(
value.is_finite()
&& if index < 2 {
value > 0.0
} else {
(0.0..=1.0).contains(&value)
},
"invalid calibration {key}"
);
ensure!(
manifest[key].as_f64() == Some(value),
"manifest disagrees with decision_config.json: {key}"
);
values[index] = value;
}
let [temperature, mm_temperature, _threshold] = values;
let config: Value = serde_json::from_slice(&std::fs::read(dir.join("config.json"))?)?;
let image_token = config["image_token_id"]
.as_u64()
Expand Down
29 changes: 28 additions & 1 deletion tests/jemm/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def setUp(self):
self.write_adapter()
self.config = {"peft_type": "LORA", "base_model_name_or_path": EXPORT.BASE_MODEL_ID, "r": 16, "lora_alpha": 32}
(self.adapter / "adapter_config.json").write_text(json.dumps(self.config))
(self.adapter / "decision_config.json").write_text(json.dumps(EXPORT.CALIBRATION))
(self.adapter / "decision_config.json").write_text(json.dumps({"temperature": 1.3480874159655591,
"mm_temperature": 1.3954832341582943,
"threshold": 0.9872681877423998}))
self.refresh_inventory()

def tearDown(self):
Expand Down Expand Up @@ -103,6 +105,31 @@ def test_preserves_original_base_and_fp32_adapter_without_merging(self):
with safe_open(str(self.out / "vision.safetensors"), framework="pt", device="cpu") as reader:
self.assertTrue(torch.equal(reader.get_tensor("model.visual.patch_embed.weight"), torch.ones(2, 2, dtype=torch.bfloat16)))

def test_v2_export_uses_checksum_covered_file_calibration(self):
calibration = {"temperature": 2., "mm_temperature": 3., "threshold": 0.5}
source = self.adapter / "decision_config.json"
source.write_text(json.dumps(calibration))
self.refresh_inventory()
manifest = self.run_export()
self.assertEqual(manifest["format"], "jemm-native/2")
self.assertEqual(manifest["lora_mode"], "unmerged_fp32")
self.assertEqual({key: manifest[key] for key in calibration}, calibration)
self.assertEqual((self.out / source.name).read_bytes(), source.read_bytes())
self.assertEqual(manifest["export_sha256"][source.name], EXPORT.sha256(source))

def test_invalid_file_calibration_is_refused_before_output_creation(self):
for key, value in [("temperature", 0), ("temperature", True),
("mm_temperature", -1), ("threshold", 1.1),
("threshold", float("nan")), ("temperature", None)]:
with self.subTest(key=key, value=value):
calibration = {"temperature": 2., "mm_temperature": 3., "threshold": 0.5}
calibration[key] = value
(self.adapter / "decision_config.json").write_text(json.dumps(calibration))
self.refresh_inventory()
with self.assertRaisesRegex(ValueError, "invalid decision calibration"):
self.run_export()
self.assertFalse(self.out.exists())

def test_cross_device_copy_fallback_preserves_identical_source_bytes(self):
with patch.object(EXPORT.os, "link", side_effect=OSError(errno.EXDEV, "cross-device")):
manifest = self.run_export()
Expand Down
56 changes: 54 additions & 2 deletions tests/jemm/tokenization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ fn fixture() -> (Fixture, Value) {
)
.unwrap();
let manifest = json!({"format":"jemm-native/2","model_id":"JEMM","base_model_id":"Qwen/Qwen3.8-27B","base_revision":contract::BASE_REVISION,"checkpoint_revision":contract::CHECKPOINT_REVISION,"source_revision":contract::SOURCE_REVISION,"max_tokens":8192,"max_mm_tokens":3072,"label_token_ids":(1..=32).collect::<Vec<_>>(),"chat_prefix":format!("<|im_start|>system\n{}<|im_end|>\n<|im_start|>user\n",contract::SYSTEM),"chat_suffix":"<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n","temperature":1.3480874159655591,"mm_temperature":1.3954832341582943,"threshold":0.9872681877423998});
std::fs::write(
dir.join("decision_config.json"),
serde_json::to_vec(&json!({"temperature":manifest["temperature"],
"mm_temperature":manifest["mm_temperature"], "threshold":manifest["threshold"]}))
.unwrap(),
)
.unwrap();
(Fixture(dir), manifest)
}
#[test]
Expand Down Expand Up @@ -86,15 +93,60 @@ fn exported_tokenizer_truncation_and_padding_cannot_change_request_tokens() {
assert!(prepared.inputs[0].ids.len() > 3);
}
#[test]
fn rejects_custom_calibration_for_pinned_adapter() {
fn rejects_manifest_calibration_disagreement() {
let (f, mut m) = fixture();
m["mm_temperature"] = json!(1.);
assert!(
Processor::load(&f.0, &m)
.err()
.unwrap()
.to_string()
.contains("pinned JEMM calibration")
.contains("manifest disagrees with decision_config.json")
);
}

#[test]
fn file_driven_calibration_must_be_finite_in_range_and_agree_with_manifest() {
for (field, invalid) in [
("temperature", json!(0.0)),
("mm_temperature", json!(-1.0)),
("threshold", json!(1.1)),
] {
let (f, mut m) = fixture();
m[field] = invalid;
std::fs::write(
f.0.join("decision_config.json"),
serde_json::to_vec(&json!({"temperature":m["temperature"],
"mm_temperature":m["mm_temperature"], "threshold":m["threshold"]}))
.unwrap(),
)
.unwrap();
assert!(
Processor::load(&f.0, &m)
.err()
.unwrap()
.to_string()
.contains("invalid calibration")
);
}
let (f, mut m) = fixture();
m["temperature"] = json!(2.0);
m["mm_temperature"] = json!(3.0);
m["threshold"] = json!(0.5);
std::fs::write(
f.0.join("decision_config.json"),
serde_json::to_vec(&json!({"temperature":2.0,"mm_temperature":3.0,"threshold":0.5}))
.unwrap(),
)
.unwrap();
let p = Processor::load(&f.0, &m).unwrap();
let prepared = p
.prepare(br#"{"questions":{"q":{"type":"noul"}}}"#)
.unwrap();
let response = prepared.context.finish(vec![vec![2.0, 0.0]]).unwrap();
assert!(
(response["answers"]["q"]["noul"].as_f64().unwrap() - 1.0 / (1.0 + (-1.0f64).exp())).abs()
< 1e-12
);
}

Expand Down
Loading