11# SPDX-License-Identifier: MPL-2.0
22# Axiom.jl HuggingFace Integration
33#
4- # Import pretrained models from HuggingFace Hub with security verification .
4+ # Import pretrained models from HuggingFace into Axiom .
55#
6- # Current Status (v0.2.0):
7- # ✓ HuggingFace Hub API integration
8- # ✓ Model metadata fetching
9- # ✓ File downloading with caching
10- # ✓ Architecture detection (BERT, RoBERTa, GPT-2, ViT, ResNet)
11- # ✓ Security verification (@prove integration)
12- # ✓ SHA256 checksum verification
13- # ✓ Architecture conversion (BERT, RoBERTa, GPT-2, ViT, ResNet)
14- # ✓ SafeTensors weight loading (pickle-free)
15- # ⚠ PyTorch .bin weight loading (requires Python conversion to SafeTensors)
16- # ✗ Tokenizer support (use Transformers.jl instead)
6+ # Status is stated honestly by capability, not as a flat "✓ done" list — the
7+ # offline core is wired into Axiom and exercised by the test suite
8+ # (test/integrations/huggingface_tests.jl); the hub-fetch layer requires the
9+ # network and is therefore NOT exercised in CI.
1710#
18- # Recommended Workflow:
19- # 1. Export HF model to ONNX: model.save_pretrained("model", export=True)
20- # 2. Import via Axiom: model = load_onnx("model/model.onnx")
21- # 3. Verify: verify(model, properties=[FiniteOutput(), ValidProbabilities()])
11+ # WIRED + TESTED (offline, network-free):
12+ # - Architecture detection from a config Dict (detect_architecture).
13+ # - Model building into an Axiom Pipeline for BERT / GPT-2 / RoBERTa / ViT /
14+ # ResNet / Llama / Whisper, plus a generic-transformer fallback
15+ # (build_model_from_config and the build_* builders).
16+ # - SafeTensors dtype mapping and header parsing (_safetensors_dtype,
17+ # _load_safetensors!); pickle-free.
18+ # - A structural verification report (verify_imported_model).
2219#
23- # Future Work:
24- # - Full PyTorch .bin weight loading
25- # - Complete architecture builders (GPT-2, ViT, ResNet)
26- # - Integrated tokenizer support
27- # - Quantization-aware conversion
28- # - Model card parsing for metadata
20+ # PRESENT, requires network (NOT exercised in CI):
21+ # - Hub metadata fetch (get_model_info) and file download (download_file),
22+ # and the from_pretrained orchestrator that drives them. These need network
23+ # access and, for private models, the AXIOM_HF_TOKEN environment variable.
24+ # - SHA256 checksum verification of a downloaded file (verify_model_hash).
25+ #
26+ # NOT implemented (honest gaps, not silent failures):
27+ # - Tokenizers — use Transformers.jl (load_tokenizer is a documented stub).
28+ # - PyTorch .bin (Python pickle) weight loading — convert to SafeTensors first.
29+ # - The `@prove` lines inside verify_imported_model are PLACEHOLDER comments,
30+ # not active proofs; do not treat that report as formal verification.
31+ #
32+ # Roadmap: full .bin support, richer architecture builders, integrated
33+ # tokenizers, quantization-aware conversion, model-card metadata parsing.
2934
3035module HuggingFaceCompat
3136
3237using HTTP
33- using JSON3
38+ using JSON
3439using SHA
3540using .. Axiom
36-
37- export from_pretrained, load_tokenizer, verify_model
41+ # Explicitly bring in the Axiom internals this module uses that are not part of
42+ # Axiom's exported surface (so `using ..Axiom` alone would not see them):
43+ # Pipeline — the layer-container type (`const Sequential = Pipeline`)
44+ # AbstractLayer — element type of the layer vectors the builders assemble
45+ # parameters — per-layer parameter accessor used by the weight loaders
46+ using .. Axiom: Pipeline, AbstractLayer, parameters
47+
48+ # Public surface. (`verify_model` was previously exported but never defined —
49+ # an undefined export; removed. Hash verification is `verify_model_hash`, kept
50+ # internal to the submodule.)
51+ export from_pretrained, load_tokenizer
3852
3953# HuggingFace Hub API endpoint
4054const HF_HUB_URL = " https://huggingface.co"
@@ -110,7 +124,7 @@ function from_pretrained(
110124 end
111125
112126 # Load configuration
113- config = JSON3 . read (read (config_path, String))
127+ config = JSON . parse (read (config_path, String))
114128
115129 # Convert architecture
116130 architecture = detect_architecture (config)
@@ -175,14 +189,14 @@ function get_model_info(model_id::String, revision::String)
175189
176190 try
177191 response = HTTP. get (url, headers= headers)
178- data = JSON3 . read (String (response. body))
192+ data = JSON . parse (String (response. body))
179193
180194 ModelInfo (
181195 model_id,
182196 revision,
183- get (data, : architecture , " unknown" ),
184- get (data, : num_parameters , 0 ),
185- get (data, : sha256 , " " ),
197+ get (data, " architecture" , " unknown" ),
198+ get (data, " num_parameters" , 0 ),
199+ get (data, " sha256" , " " ),
186200 " "
187201 )
188202 catch e
@@ -239,16 +253,16 @@ Detect model architecture from config.
239253"""
240254function detect_architecture (config)
241255 # Check for architecture hints in config
242- if haskey (config, : model_type )
243- return String (config. model_type)
256+ if haskey (config, " model_type" )
257+ return String (config[ " model_type" ] )
244258 end
245259
246- if haskey (config, : architectures ) && ! isempty (config. architectures)
247- return String (config. architectures[1 ])
260+ if haskey (config, " architectures" ) && ! isempty (config[ " architectures" ] )
261+ return String (config[ " architectures" ] [1 ])
248262 end
249263
250264 # Heuristics based on config structure
251- if haskey (config, : num_hidden_layers ) && haskey (config, : num_attention_heads )
265+ if haskey (config, " num_hidden_layers" ) && haskey (config, " num_attention_heads" )
252266 return " transformer"
253267 end
254268
@@ -288,11 +302,11 @@ end
288302Build BERT architecture.
289303"""
290304function build_bert (config)
291- hidden_size = get (config, : hidden_size , 768 )
292- num_layers = get (config, : num_hidden_layers , 12 )
293- num_heads = get (config, : num_attention_heads , 12 )
294- intermediate_size = get (config, : intermediate_size , 3072 )
295- vocab_size = get (config, : vocab_size , 30522 )
305+ hidden_size = get (config, " hidden_size" , 768 )
306+ num_layers = get (config, " num_hidden_layers" , 12 )
307+ num_heads = get (config, " num_attention_heads" , 12 )
308+ intermediate_size = get (config, " intermediate_size" , 3072 )
309+ vocab_size = get (config, " vocab_size" , 30522 )
296310 head_dim = hidden_size ÷ num_heads
297311
298312 @info " Building BERT model" hidden_size num_layers num_heads vocab_size
@@ -330,10 +344,10 @@ end
330344Build GPT-2 architecture.
331345"""
332346function build_gpt2 (config)
333- hidden_size = get (config, : n_embd , 768 )
334- num_layers = get (config, : n_layer , 12 )
335- vocab_size = get (config, : vocab_size , 50257 )
336- max_seq_len = get (config, : n_positions , 1024 )
347+ hidden_size = get (config, " n_embd" , 768 )
348+ num_layers = get (config, " n_layer" , 12 )
349+ vocab_size = get (config, " vocab_size" , 50257 )
350+ max_seq_len = get (config, " n_positions" , 1024 )
337351 intermediate_size = hidden_size * 4
338352
339353 @info " Building GPT-2 model" hidden_size num_layers vocab_size
@@ -379,14 +393,14 @@ end
379393Build Vision Transformer architecture.
380394"""
381395function build_vit (config)
382- hidden_size = get (config, : hidden_size , 768 )
383- num_layers = get (config, : num_hidden_layers , 12 )
384- num_heads = get (config, : num_attention_heads , 12 )
385- intermediate_size = get (config, : intermediate_size , 3072 )
386- image_size = get (config, : image_size , 224 )
387- patch_size = get (config, : patch_size , 16 )
388- num_channels = get (config, : num_channels , 3 )
389- num_labels = get (config, : num_labels , 1000 )
396+ hidden_size = get (config, " hidden_size" , 768 )
397+ num_layers = get (config, " num_hidden_layers" , 12 )
398+ num_heads = get (config, " num_attention_heads" , 12 )
399+ intermediate_size = get (config, " intermediate_size" , 3072 )
400+ image_size = get (config, " image_size" , 224 )
401+ patch_size = get (config, " patch_size" , 16 )
402+ num_channels = get (config, " num_channels" , 3 )
403+ num_labels = get (config, " num_labels" , 1000 )
390404
391405 num_patches = (image_size ÷ patch_size)^ 2
392406
@@ -421,12 +435,12 @@ Build ResNet architecture.
421435"""
422436function build_resnet (config)
423437 # ResNet config may use different key names depending on HF model card
424- num_channels = get (config, : num_channels , 3 )
425- num_labels = get (config, : num_labels , 1000 )
438+ num_channels = get (config, " num_channels" , 3 )
439+ num_labels = get (config, " num_labels" , 1000 )
426440
427441 # Detect ResNet variant from config
428- depths = get (config, : depths , [3 , 4 , 6 , 3 ]) # ResNet-50 default
429- hidden_sizes = get (config, : hidden_sizes , [256 , 512 , 1024 , 2048 ])
442+ depths = get (config, " depths" , [3 , 4 , 6 , 3 ]) # ResNet-50 default
443+ hidden_sizes = get (config, " hidden_sizes" , [256 , 512 , 1024 , 2048 ])
430444
431445 @info " Building ResNet model" depths hidden_sizes
432446
@@ -467,13 +481,13 @@ end
467481Build LLaMA architecture (decoder-only transformer with RMSNorm and SwiGLU MLP).
468482"""
469483function build_llama (config)
470- hidden_size = get (config, : hidden_size , 4096 )
471- num_layers = get (config, : num_hidden_layers , 32 )
472- num_heads = get (config, : num_attention_heads , 32 )
473- intermediate_size = get (config, : intermediate_size , 11008 )
474- vocab_size = get (config, : vocab_size , 32000 )
484+ hidden_size = get (config, " hidden_size" , 4096 )
485+ num_layers = get (config, " num_hidden_layers" , 32 )
486+ num_heads = get (config, " num_attention_heads" , 32 )
487+ intermediate_size = get (config, " intermediate_size" , 11008 )
488+ vocab_size = get (config, " vocab_size" , 32000 )
475489 # num_key_value_heads for GQA (defaults to num_heads for MHA)
476- num_kv_heads = get (config, : num_key_value_heads , num_heads)
490+ num_kv_heads = get (config, " num_key_value_heads" , num_heads)
477491
478492 @info " Building LLaMA model" hidden_size num_layers num_heads num_kv_heads vocab_size
479493
@@ -513,15 +527,15 @@ end
513527Build Whisper architecture (encoder-decoder transformer for speech recognition).
514528"""
515529function build_whisper (config)
516- d_model = get (config, : d_model , 512 )
517- encoder_layers = get (config, : encoder_layers , 6 )
518- decoder_layers = get (config, : decoder_layers , 6 )
519- encoder_attention_heads = get (config, : encoder_attention_heads , 8 )
520- decoder_attention_heads = get (config, : decoder_attention_heads , 8 )
521- encoder_ffn_dim = get (config, : encoder_ffn_dim , 2048 )
522- decoder_ffn_dim = get (config, : decoder_ffn_dim , 2048 )
523- vocab_size = get (config, : vocab_size , 51865 )
524- num_mel_bins = get (config, : num_mel_bins , 80 )
530+ d_model = get (config, " d_model" , 512 )
531+ encoder_layers = get (config, " encoder_layers" , 6 )
532+ decoder_layers = get (config, " decoder_layers" , 6 )
533+ encoder_attention_heads = get (config, " encoder_attention_heads" , 8 )
534+ decoder_attention_heads = get (config, " decoder_attention_heads" , 8 )
535+ encoder_ffn_dim = get (config, " encoder_ffn_dim" , 2048 )
536+ decoder_ffn_dim = get (config, " decoder_ffn_dim" , 2048 )
537+ vocab_size = get (config, " vocab_size" , 51865 )
538+ num_mel_bins = get (config, " num_mel_bins" , 80 )
525539
526540 @info " Building Whisper model" d_model encoder_layers decoder_layers vocab_size
527541
@@ -632,7 +646,7 @@ function _load_safetensors!(model, path::String)
632646 data = read (path)
633647 header_size = reinterpret (UInt64, data[1 : 8 ])[1 ]
634648 header_json = String (data[9 : 8 + header_size])
635- header = JSON3 . read (header_json)
649+ header = JSON . parse (header_json)
636650 tensor_data_start = 8 + header_size
637651
638652 # Build name → array mapping
@@ -641,9 +655,9 @@ function _load_safetensors!(model, path::String)
641655 name_str = String (name)
642656 name_str == " __metadata__" && continue
643657
644- dtype_str = String (meta. dtype)
645- shape = Tuple (meta. shape)
646- offsets = meta. data_offsets # [start, end] relative to tensor data
658+ dtype_str = String (meta[ " dtype" ] )
659+ shape = Tuple (meta[ " shape" ] )
660+ offsets = meta[ " data_offsets" ] # [start, end] relative to tensor data
647661
648662 # Parse dtype
649663 T = _safetensors_dtype (dtype_str)
@@ -731,7 +745,7 @@ function _set_layer_param!(layer, param_name::Symbol, data::Array)
731745end
732746
733747function _load_json_weights! (model, path:: String )
734- data = JSON3 . read (read (path, String))
748+ data = JSON . parse (read (path, String))
735749 tensors = Dict {String, Array} ()
736750 for (name, tensor_data) in pairs (data)
737751 if tensor_data isa AbstractVector
0 commit comments