alexwengg commited on
Commit
2def9fa
·
verified ·
1 Parent(s): ce235ae

Publish NanoJev Core ML conversion source without trained weights

Browse files
source/STATUS.md CHANGED
@@ -1,6 +1,7 @@
1
  # NanoJev status
2
 
3
  - Pinned current checkpoint and audited native source; full checkpoint SHA256 matches upstream and contains 596,250,498 trained parameters across 322 tensors. Historical tracker revision unresolved.
4
- - Pinned full checkpoint downloaded. Native Qwen3 encoder plus set-head exporter matched the trained model on a 4-candidate Choice request: same argmax, maximum logit error `1.07e-6`.
5
- - Core ML export and runtime parity for Choice, Boolean, and Score remain pending.
 
6
  - Trained-weight redistribution license unresolved. Public HF model artifact is not yet authorized by the source metadata.
 
1
  # NanoJev status
2
 
3
  - Pinned current checkpoint and audited native source; full checkpoint SHA256 matches upstream and contains 596,250,498 trained parameters across 322 tensors. Historical tracker revision unresolved.
4
+ - Pinned full checkpoint downloaded. Native Qwen3 encoder plus decision-head exporter matched the trained model on Choice, Boolean, and Score requests: same argmax for all three, maximum logit error `1.67e-6`.
5
+ - FP16 Core ML L128/K4 encoder and full decision-head packages exported in 22.7 seconds. Encoder package is about 1.1 GiB; head package is about 420 KiB.
6
+ - Core ML runtime parity for Choice, Boolean, and Score remains pending.
7
  - Trained-weight redistribution license unresolved. Public HF model artifact is not yet authorized by the source metadata.
source/convert-coreml.py CHANGED
@@ -46,6 +46,7 @@ def main() -> None:
46
  parser.add_argument("--candidates", type=int, default=4)
47
  parser.add_argument("--output-dir", type=Path, default=ROOT / "build")
48
  parser.add_argument("--parity-only", action="store_true")
 
49
  args = parser.parse_args()
50
  if args.candidates < 2:
51
  raise ValueError("candidate bucket must be at least 2 for Boolean output")
@@ -62,6 +63,11 @@ def main() -> None:
62
  args.output_dir.mkdir(parents=True, exist_ok=True)
63
  started = time.perf_counter()
64
  encoder_trace = torch.jit.trace(encoder, tuple(torch.from_numpy(value) for value in inputs.values()))
 
 
 
 
 
65
  encoder_coreml = ct.convert(
66
  encoder_trace,
67
  convert_to="mlprogram",
 
46
  parser.add_argument("--candidates", type=int, default=4)
47
  parser.add_argument("--output-dir", type=Path, default=ROOT / "build")
48
  parser.add_argument("--parity-only", action="store_true")
49
+ parser.add_argument("--trace-only", action="store_true")
50
  args = parser.parse_args()
51
  if args.candidates < 2:
52
  raise ValueError("candidate bucket must be at least 2 for Boolean output")
 
63
  args.output_dir.mkdir(parents=True, exist_ok=True)
64
  started = time.perf_counter()
65
  encoder_trace = torch.jit.trace(encoder, tuple(torch.from_numpy(value) for value in inputs.values()))
66
+ if args.trace_only:
67
+ for index, node in enumerate(encoder_trace.inlined_graph.nodes()):
68
+ if node.kind() == "aten::Int":
69
+ print(f"trace node {index}: {node} scope={node.scopeName()}", flush=True)
70
+ return
71
  encoder_coreml = ct.convert(
72
  encoder_trace,
73
  convert_to="mlprogram",
source/export_model.py CHANGED
@@ -16,8 +16,7 @@ def rotate_half(value: torch.Tensor) -> torch.Tensor:
16
  return torch.cat((-second, first), dim=-1)
17
 
18
 
19
- def rms_norm(hidden: torch.Tensor, norm: nn.Module) -> torch.Tensor:
20
- width = norm.weight.numel()
21
  doubled = torch.cat((hidden, -hidden), dim=-1)
22
  normalized = F.layer_norm(doubled, (width * 2,), eps=norm.variance_epsilon)
23
  return normalized[..., :width] * norm.weight
@@ -51,7 +50,7 @@ class NanoEncoder(nn.Module):
51
 
52
  def _layer(self, layer: nn.Module, hidden: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
53
  attention = layer.self_attn
54
- normalized = rms_norm(hidden, layer.input_layernorm)
55
  q = attention.q_proj(normalized).view(self.candidates, self.length, self.heads, self.head_dim)
56
  k = attention.k_proj(normalized).view(self.candidates, self.length, self.kv_heads, self.head_dim)
57
  v = attention.v_proj(normalized).view(self.candidates, self.length, self.kv_heads, self.head_dim)
@@ -68,7 +67,7 @@ class NanoEncoder(nn.Module):
68
  self.candidates, self.length, self.heads * self.head_dim
69
  )
70
  hidden = hidden + attention.o_proj(attended)
71
- return hidden + layer.mlp(rms_norm(hidden, layer.post_attention_layernorm))
72
 
73
  def forward(
74
  self, input_ids: torch.Tensor, attention_mask: torch.Tensor, eos_map: torch.Tensor
@@ -78,7 +77,7 @@ class NanoEncoder(nn.Module):
78
  hidden = self.embed_tokens(input_ids.long())
79
  for layer in self.layers:
80
  hidden = self._layer(layer, hidden, mask)
81
- hidden = rms_norm(hidden, self.norm).float()
82
  return torch.matmul(eos_map, hidden).transpose(0, 1)
83
 
84
 
 
16
  return torch.cat((-second, first), dim=-1)
17
 
18
 
19
+ def rms_norm(hidden: torch.Tensor, norm: nn.Module, width: int) -> torch.Tensor:
 
20
  doubled = torch.cat((hidden, -hidden), dim=-1)
21
  normalized = F.layer_norm(doubled, (width * 2,), eps=norm.variance_epsilon)
22
  return normalized[..., :width] * norm.weight
 
50
 
51
  def _layer(self, layer: nn.Module, hidden: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
52
  attention = layer.self_attn
53
+ normalized = rms_norm(hidden, layer.input_layernorm, self.hidden)
54
  q = attention.q_proj(normalized).view(self.candidates, self.length, self.heads, self.head_dim)
55
  k = attention.k_proj(normalized).view(self.candidates, self.length, self.kv_heads, self.head_dim)
56
  v = attention.v_proj(normalized).view(self.candidates, self.length, self.kv_heads, self.head_dim)
 
67
  self.candidates, self.length, self.heads * self.head_dim
68
  )
69
  hidden = hidden + attention.o_proj(attended)
70
+ return hidden + layer.mlp(rms_norm(hidden, layer.post_attention_layernorm, self.hidden))
71
 
72
  def forward(
73
  self, input_ids: torch.Tensor, attention_mask: torch.Tensor, eos_map: torch.Tensor
 
77
  hidden = self.embed_tokens(input_ids.long())
78
  for layer in self.layers:
79
  hidden = self._layer(layer, hidden, mask)
80
+ hidden = rms_norm(hidden, self.norm, self.hidden).float()
81
  return torch.matmul(eos_map, hidden).transpose(0, 1)
82
 
83