Runtime de inferencia puro NumPy para extraccion de pesos, capas y activaciones de LLMs y CNNs
Project description
micronnx
Capa de extracción de pesos, capas y activaciones para sistemas de fusión de modelos. Parte del ecosistema UFM (Unified Fusion Model).
micronnx no es un framework de entrenamiento ni un motor de inferencia general. Es una pieza de recolección: carga cualquier modelo desde cualquier formato, extrae sus pesos y activaciones capa a capa, y los expone en una estructura unificada lista para que UFM tome decisiones de fusión.
Sin PyTorch. Sin TensorFlow. Solo NumPy.
Instalación
pip install micronnx
¿Qué hace micronnx?
- Carga de modelos: GGUF (Q2_K a Q6_K, Q8_0, Q8_1, BF16, F16, F32), SafeTensors, HDF5/Keras, NPY/NPZ — todos lazy, sin cargar nada hasta pedirlo
- Extracción de pesos: todos los tensores normalizados a float32, etiquetados con rol y capa
- Extracción de activaciones: capa a capa — embed, attn, ffn, residual, norm, pool
- Exportación unificada: uno o varios modelos a un solo .npz con índice completo (schema y hp incluidos)
- Carga desde .npz: NpzModelLoader detecta arquitectura automáticamente (31 familias) incluso en .npz de versiones antiguas sin schema_name
- Ops vectorizadas: RMSNorm, Attention GQA, RoPE, SwiGLU, GeGLU, Conv2D, GroupNorm y más — sin loops Python
Formatos soportados
GGUF (.gguf) Q2_K, Q3_K, Q4_0, Q4_1, Q4_K, Q5_0, Q5_1, Q5_K, Q6_K Q8_0, Q8_1 (corregido en v2), BF16, F16, F32
SafeTensors (.safetensors) F32, F16, BF16 (corregido en v2), F64 convertido a F32 automaticamente I8, I16, I32, I64 sin conversion
HDF5 / Keras (.h5, .keras) float32, float64 convertido a float32, lazy loading
NumPy (.npy, .npz) float32, float16 convertido a float32, mmap lazy para .npz
Arquitecturas detectadas automaticamente
GGUF: gguf_llama, gguf_gemma, gguf_gemma2, gguf_phi3 gguf_falcon, gguf_falcon40b, gguf_gpt2, gguf_gpt_neox gguf_bloom, gguf_mpt, gguf_bert, gguf_mixtral gguf_qwen_moe, gguf_olmo2
HuggingFace SafeTensors: hf_llama, hf_gemma, hf_gemma2, hf_phi2, hf_phi3 hf_falcon, hf_falcon40b, hf_gpt2, hf_gpt_neox hf_bloom, hf_mpt, hf_bert, hf_mixtral, hf_qwen_moe hf_chatglm, hf_cohere, hf_olmo2, hf_internlm2 hf_baichuan, hf_stablelm, hf_minicpm, hf_xverse, hf_t5
Cubre: LLaMA 1/2/3/3.1/3.2, Mistral, Qwen 1/2/2.5/3, SmolLM, Gemma 1/2/3, Phi-2/3/3.5/4, Falcon 7B/40B, GPT-2, Starcoder2, GPT-NeoX, Pythia, BLOOM, MPT, BERT, RoBERTa, DeBERTa, Mixtral, Qwen2-MoE, DeepSeek V2/V3, ChatGLM4, Cohere Command-R, Aya, OLMo2, InternLM2, Baichuan2, StableLM, MiniCPM, XVERSE, T5, Flan-T5, MobileNetV1, MobileNetV2, MobileNetV3
Uso rapido
import micronnx as nx
loader = nx.GGUFLoader("model.gguf")
schema, hp = nx.detect_schema_gguf("model.gguf")
runner = nx.ModelRunner(loader, schema, hp)
logits = runner.forward(input_ids)
Exportar modelos a .npz
import micronnx as nx
# Un solo modelo
nx.export_to_npz("model.gguf", "model.npz")
# Varios modelos, un .npz por cada uno
nx.export_to_npz(
["model.gguf", "model.safetensors", "mobilenet.h5"],
"outputs/"
)
# Varios modelos en un solo .npz fusionado
nx.export_to_npz(
["model.gguf", "model.safetensors", "mobilenet.h5"],
"outputs/merged.npz",
merge=True
)
# Con string separado por comas
nx.export_to_npz("model.gguf, model.safetensors", "outputs/", merge=False)
Inspeccionar un .npz
import micronnx as nx
nx.inspect_npz("outputs/merged.npz")
# Merged : 2 modelos
# Total : 269,030,016 params | 513.14 MB float16
# [SmolLM2-135M] 272 tensores | 30 capas | 256.57 MB | schema: gguf_llama
# [model] 272 tensores | 30 capas | 256.57 MB | schema: hf_llama
# Leer el indice sin cargar ningun tensor
idx = nx.load_index("outputs/merged.npz")
print(idx["n_models"])
print(idx["total_params"])
print(idx["models"].keys())
# Schema e hiperparametros de cada modelo
for name, info in idx["models"].items():
print(name, info["schema_name"], info["hp"])
Cargar desde .npz sin archivo original
import micronnx as nx
# Ver que modelos hay
models = nx.list_models("outputs/merged.npz")
for name, info in models.items():
print(name, info["schema_name"], info["has_hp"], info["has_activations"])
# Cargar directamente — detecta arquitectura automaticamente
loader = nx.NpzModelLoader("outputs/merged.npz", "SmolLM2-135M-Instruct-Q4_K_M")
schema, hp = nx.detect_schema_npz("outputs/merged.npz", "SmolLM2-135M-Instruct-Q4_K_M")
runner = nx.ModelRunner(loader, schema, hp, max_seq=512)
# Si el .npz no tiene schema_name (version antigua),
# se detecta automaticamente desde los nombres de tensores sin el archivo original
Extraer activaciones — LLM
import numpy as np
import micronnx as nx
# Desde archivo original
loader = nx.GGUFLoader("SmolLM2-135M-Instruct-Q4_K_M.gguf")
schema, hp = nx.detect_schema_gguf("SmolLM2-135M-Instruct-Q4_K_M.gguf")
runner = nx.ModelRunner(loader, schema, hp, max_seq=512)
# O desde .npz sin archivo original
loader = nx.NpzModelLoader("outputs/merged.npz", "SmolLM2-135M-Instruct-Q4_K_M")
schema, hp = nx.detect_schema_npz("outputs/merged.npz", "SmolLM2-135M-Instruct-Q4_K_M")
runner = nx.ModelRunner(loader, schema, hp, max_seq=512)
# Todas las activaciones
ext = nx.ActivationExtractor(runner)
ext.run(np.array([[1, 2, 3, 4, 5]], dtype=np.int64))
print(ext.keys())
# embed, attn_norm_0, post_attn_0, residual_attn_0,
# ffn_norm_0, post_ffn_0, residual_ffn_0, ..., final_norm
# One-shot sin instanciar
logits, acts = nx.ActivationExtractor.extract(
runner,
np.array([[1, 2, 3]], dtype=np.int64)
)
# Solo algunos hooks
ext = nx.ActivationExtractor(runner, hooks=["post_attn", "residual_ffn"])
ext.run(np.array([[1, 2, 3, 4, 5]], dtype=np.int64))
# Reducir la dimension de secuencia
ext = nx.ActivationExtractor(runner, reduce="last") # ultimo token
ext = nx.ActivationExtractor(runner, reduce="mean") # media de tokens
# Solo capas pares
ext = nx.ActivationExtractor(runner, layer_fn=lambda i: i % 2 == 0)
# Solo capas especificas por indice
ext = nx.ActivationExtractor(runner, layer_fn=[0, 5, 11, 23])
# Acceso a activaciones
hidden = ext.get("final_norm") # KeyError claro si no fue capturado
attn5 = ext.get("post_attn_5")
for key, tensor in ext.items():
print(key, tensor.shape)
Extraer activaciones — BERT
import numpy as np
import micronnx as nx
loader = nx.SafeTensorsLoader("bert-base-uncased/model.safetensors")
schema, hp = nx.detect_schema_safetensors("bert-base-uncased/model.safetensors")
runner = nx.ModelRunner(loader, schema, hp)
ext = nx.ActivationExtractor(runner, hooks=["post_attn", "final_norm"])
hidden = ext.run_bert(
np.array([[101, 2054, 2003, 102]], dtype=np.int64),
attention_mask=np.ones((1, 4), dtype=np.int64)
)
# One-shot para BERT
hidden, acts = nx.ActivationExtractor.extract_bert(runner, input_ids)
cls_vector = acts["final_norm"][0, 0] # token [CLS]
Extraer activaciones — CNN (MobileNet)
import numpy as np
import micronnx as nx
raw = nx.H5Loader("mobilenet_1_0_224_tf.h5")
mapped = nx.map_tensors(dict.fromkeys(raw.tensor_names), fmt="h5")
loader = nx.CanonicalLoader(raw, mapped)
runner = nx.CNNRunner(loader, n_blocks=13)
# Imagen 224x224x3 normalizada en [-1, 1]
image = np.random.uniform(-1, 1, (224, 224, 3)).astype(np.float32)
# Forward directo
probs = runner.forward(image)
print(f"clase: {probs.argmax()}, confianza: {probs.max():.3f}")
# Con extraccion de activaciones
ext = nx.CNNActivationExtractor(runner, reduce="spatial")
probs = ext.run(image)
print(ext.keys())
# stem, block_0_dw, block_0_pw, ..., block_12_pw, pooled
# One-shot
probs, acts = nx.CNNActivationExtractor.extract(runner, image)
feat = acts["block_5_pw"] # (C,) con reduce="spatial"
# MobileNetV2 / MobileNetV3
runner = nx.InvertedResidualRunner(loader, n_blocks=17)
probs = runner.forward(image)
Guardar y leer activaciones en el .npz
import numpy as np
import micronnx as nx
merged = nx.export_to_npz(
["SmolLM2-135M-Instruct-Q4_K_M.gguf", "model.safetensors"],
"outputs/merged.npz",
merge=True
)
loader = nx.NpzModelLoader(merged, "SmolLM2-135M-Instruct-Q4_K_M")
schema, hp = nx.detect_schema_npz(merged, "SmolLM2-135M-Instruct-Q4_K_M")
runner = nx.ModelRunner(loader, schema, hp)
ext = nx.ActivationExtractor(runner, reduce="last")
ext.run(np.array([[1, 2, 3]], dtype=np.int64))
nx.save_activations(merged, ext.activations, model_key="SmolLM2-135M-Instruct-Q4_K_M")
# Leer despues sin recargar el modelo
acts = nx.load_activations(merged, model_key="SmolLM2-135M-Instruct-Q4_K_M")
print(acts["final_norm"].shape)
TensorRegistry — hasta 3 modelos lazy simultaneos
import micronnx as nx
# Registrar modelos — solo indexa nombres, no carga datos
nx.registry.register("llama3", nx.GGUFLoader("llama3.gguf"), fmt="gguf")
nx.registry.register("mistral", nx.SafeTensorsLoader("mistral.st"), fmt="safetensors")
nx.registry.register("mobilenet",nx.H5Loader("mobilenet.h5"), fmt="h5")
# Consultar sin cargar nada — O(1)
nx.registry.has("llama3", "layers.5.attn.q.weight") # True / False
nx.registry.list("llama3") # lista de canonicos
# Cargar un tensor — solo aqui se lee del disco
t = nx.registry.get("llama3", "layers.5.attn.q.weight")
# Al registrar un 4to modelo, el mas antiguo se expulsa automaticamente (LRU)
nx.registry.release("mistral") # liberar manualmente
nx.registry.stats() # {"models": [...], "slots_used": 2, ...}
# O instanciar un registry propio
reg = nx.TensorRegistry()
reg.register("modelo_a", loader_a)
reg.register("modelo_b", loader_b)
Loaders directos
import micronnx as nx
# GGUF — lazy, mmap, dequantiza bajo demanda
loader = nx.GGUFLoader("model.gguf")
print(loader.tensor_names[:5])
w = loader.load("token_embd.weight")
loader.close()
# SafeTensors — lazy, mmap, BF16/F64 correctos
loader = nx.SafeTensorsLoader("model.safetensors")
print(loader.shape("model.embed_tokens.weight")) # sin cargar
print(loader.dtype("model.embed_tokens.weight")) # sin cargar
w = loader.load("model.embed_tokens.weight")
# Cargar solo tensores de atencion (ahorra 30-70% de RAM)
weights = loader.load_all(filter_fn=lambda n: "attn" in n)
# Exportar sin pico de RAM — un tensor a la vez
for name, arr in loader.stream_load(keep_float16=True):
pass # procesar arr y liberar
loader.close()
# HDF5 / Keras — lazy, sin cargar hasta .load()
loader = nx.H5Loader("mobilenet.h5")
w = loader.load("conv1/kernel:0")
attrs = loader.attributes("conv1") # metadata de la capa Keras
loader.close()
# NPY / NPZ — mmap lazy para .npz
loader = nx.NpyLoader("weights.npz")
w = loader.load("layer_0")
# Directorio de .npy (estilo JAX/Flax)
loader = nx.NpyLoader("checkpoints/")
w = loader.load("encoder/layers/0/attn/q")
# Desde .npz merged sin archivo original
loader = nx.NpzModelLoader("outputs/merged.npz", "model")
w = loader.load("model.embed_tokens.weight")
loader.close()
Detectar schema y arquitectura
import micronnx as nx
# Desde archivo original
schema, hp = nx.detect_schema_gguf("model.gguf")
print(schema)
# gguf_llama / gguf_gemma2 / gguf_phi3 / gguf_mixtral / gguf_qwen_moe ...
print(hp)
# {"n_layers": 30, "n_heads": 9, "n_kv_heads": 3, "n_embd": 576, "vocab_size": 49152}
schema, hp = nx.detect_schema_safetensors("model.safetensors")
schema, hp = nx.detect_schema_hf("model.safetensors") # alias
# Desde .npz sin archivo original
schema, hp = nx.detect_schema_npz("outputs/merged.npz", "SmolLM2-135M-Instruct-Q4_K_M")
# Ver todos los schemas disponibles
print(list(nx.SCHEMAS.keys()))
# gguf_llama, gguf_gemma, gguf_gemma2, gguf_phi3, gguf_falcon,
# gguf_mixtral, gguf_qwen_moe, hf_llama, hf_gemma2, hf_bert ...
Mapeo canonico de tensores
import micronnx as nx
# Convertir nombres originales a nombres canonicos
loader = nx.GGUFLoader("model.gguf")
mapped = nx.map_tensors(dict.fromkeys(loader.tensor_names), fmt="gguf")
# Ver tensores sin mapear
unmapped = nx.find_unmapped(dict.fromkeys(loader.tensor_names), fmt="gguf", mapped=mapped)
# Resolver embeddings atados (embed <-> head)
mapped = nx.resolve_tied_embeddings(mapped)
# Detectar formato automaticamente
fmt = nx.detect_format(dict.fromkeys(loader.tensor_names))
# CanonicalLoader: traduce nombres canonicos a originales automaticamente
canonical = nx.CanonicalLoader(loader, mapped)
w = canonical.load("layers.0.attn.q.weight") # nombre canonico
print("layers.0.attn.q.weight" in canonical) # True
Ops NumPy directas
import numpy as np
import micronnx as nx
x = np.random.randn(1, 16, 576).astype(np.float32)
w = np.ones(576, dtype=np.float32)
# Normalizacion
x = nx.rmsnorm(x, w)
x = nx.layernorm(x, w, w)
x = nx.batchnorm(x, gamma, beta, mean, var)
x = nx.group_norm(x, num_groups=8, weight=w)
x = nx.instance_norm(x)
# Activaciones
x = nx.gelu(x)
x = nx.quick_gelu(x) # CLIP, ViT
x = nx.silu(x) # LLaMA, Mistral
x = nx.mish(x) # YOLOv4
x = nx.hardswish(x) # MobileNetV3
x = nx.leaky_relu(x, 0.01) # GANs, YOLO
x = nx.prelu(x, weight) # ResNet
x = nx.elu(x)
# FFN
out = nx.swiglu(x, gate_w, up_w, down_w) # LLaMA, Mistral, Qwen
out = nx.swiglu_fused(x, gate_up_w, down_w) # Phi-3, InternLM2
out = nx.geglu(x, gate_w, up_w, down_w) # Gemma
out = nx.geglu_fused(x, gate_up_w, down_w) # T5v1.1, Flan-T5
out = nx.ffn_gelu(x, up_w, down_w) # GPT-2, BLOOM
out = nx.ffn_relu(x, up_w, down_w) # T5 original
# Atencion GQA (cubre MHA y MQA como casos especiales)
q = np.random.randn(1, 4, 9, 64).astype(np.float32)
k = np.random.randn(1, 4, 3, 64).astype(np.float32)
v = np.random.randn(1, 4, 3, 64).astype(np.float32)
out = nx.attention(q, k, v, n_heads=9, n_kv_heads=3)
# SDPA directa sin reshape de heads (ViT, CLIP)
out = nx.scaled_dot_product(q, k, v, mask=None)
# RoPE — convencion LLaMA correcta
x = np.random.randn(1, 4, 8, 64).astype(np.float32)
out = nx.rope(x, pos=0)
out = nx.rope(x, positions=np.array([0, 1, 2, 3]))
# CNN
img = np.random.randn(112, 112, 32).astype(np.float32)
filt = np.random.randn(64, 32, 3, 3).astype(np.float32) # formato PyTorch (C_out,C_in,kH,kW)
out = nx.conv2d(img, filt, stride=1, padding=1)
out = nx.depthwise_conv2d(img, dw_weight, stride=2)
out = nx.global_avg_pool(out)
out = nx.avg_pool2d(out, kernel_size=2)
out = nx.max_pool2d(out, size=2, stride=2)
out = nx.adaptive_avg_pool(out, output_size=(7, 7))
out = nx.upsample_nearest(out, scale_factor=2)
API completa
Loaders nx.GGUFLoader(path) lazy, mmap, dequant bajo demanda nx.SafeTensorsLoader(path) lazy, mmap, BF16/F64 correctos nx.H5Loader(path) lazy, float64 convertido nx.NpyLoader(path) lazy mmap para .npz, dir para .npy nx.NpzModelLoader(npz_path, model_key) desde .npz sin archivo original
Deteccion de schema nx.detect_schema_gguf(path) (schema_name, hp) nx.detect_schema_safetensors(path) (schema_name, hp) nx.detect_schema_hf(path) (schema_name, hp) nx.detect_schema_npz(npz_path, key) (schema_name, hp) desde .npz nx.detect_schema_from_tensors(names, ...) desde lista de nombres de tensores nx.list_models(npz_path) metadata de todos los modelos nx.SCHEMAS dict con todos los schemas
TensorRegistry nx.registry instancia global (hasta 3 modelos LRU) nx.TensorRegistry() instancia propia reg.register(name, loader, fmt) indexa sin cargar datos reg.get(name, canonical) carga aqui — unico punto de I/O reg.has(name, canonical) O(1) sin cargar nada reg.list(name) lista de canonicos disponibles reg.release(name) liberar slot manualmente reg.stats() info de slots usados
Runtime LLM nx.ModelRunner(loader, schema_name, hp, max_seq=2048) nx.ActivationExtractor(runner, hooks=None, reduce=None, layer_fn=None, keep_copy=True) hooks: ["embed","post_attn","residual_ffn","final_norm",...] reduce: None | "last" | "mean" layer_fn: callable(i)->bool | list[int] nx.ActivationExtractor.extract(runner, input_ids, hooks, reduce) nx.ActivationExtractor.extract_bert(runner, input_ids, ...) nx.RoPECache / nx.KVCache / nx.WeightCache
Runtime CNN nx.CNNRunner(loader, n_blocks=13) MobileNetV1 nx.InvertedResidualRunner(loader, n_blocks=17) MobileNetV2 / V3 nx.CNNActivationExtractor(runner, reduce="spatial") nx.CNNActivationExtractor.extract(runner, image) nx.CanonicalLoader(raw_loader, mapped)
Canonico nx.map_tensors(tensors, fmt, strict=False) nx.find_unmapped(tensors, fmt, mapped) nx.resolve_tied_embeddings(mapped) nx.detect_format(tensors) nx.CanonicalTensor
Exportador nx.export_to_npz(src, dst, fmt=None, merge=False, verbose=True) nx.load_index(path) nx.inspect_npz(path, n=20) nx.save_activations(path, acts, model_key=None) nx.load_activations(path, model_key=None)
Ops — normalizacion nx.rmsnorm(x, weight, eps=1e-6) nx.layernorm(x, weight, bias=None, eps=1e-5) nx.batchnorm(x, gamma, beta, mean, var, eps=1e-3) nx.group_norm(x, num_groups, weight=None, bias=None) nx.instance_norm(x, weight=None, bias=None)
Ops — activaciones nx.softmax(x, axis=-1) nx.sigmoid(x) nx.relu(x) nx.leaky_relu(x, negative_slope=0.01) nx.elu(x, alpha=1.0) nx.prelu(x, weight) nx.gelu(x) nx.quick_gelu(x) nx.silu(x) nx.mish(x) nx.hardswish(x)
Ops — lineales nx.linear(x, weight, bias=None) nx.embedding(idx, weight)
Ops — FFN nx.swiglu(x, gate_w, up_w, down_w) nx.swiglu_fused(x, gate_up_w, down_w) nx.geglu(x, gate_w, up_w, down_w) nx.geglu_fused(x, gate_up_w, down_w) nx.ffn_gelu(x, up_w, down_w) nx.ffn_relu(x, up_w, down_w)
Ops — atencion nx.attention(q, k, v, n_heads, n_kv_heads, mask=None) nx.scaled_dot_product(q, k, v, mask=None) nx.rope(x, pos=None, base=10000.0, positions=None)
Ops — CNN nx.conv2d(x, weight, bias=None, stride=1, padding=0) nx.depthwise_conv2d(x, weight, stride=1, padding=1) nx.global_avg_pool(x) nx.avg_pool2d(x, kernel_size=2, stride=None, padding=0) nx.max_pool2d(x, size=2, stride=2) nx.adaptive_avg_pool(x, output_size) nx.upsample_nearest(x, scale_factor=2)
Correcciones v2
BF16 en SafeTensors — resultado silenciosamente incorrecto en v1. v1 convertia el valor numerico uint16 a uint32 antes de shiftear, lo cual es matematicamente incorrecto. v2 copia los bytes directamente a los bytes altos del float32, que es la definicion de BF16. Afectaba a: Mistral, LLaMA-3, Gemma, Phi y cualquier modelo HF en BF16.
Q8_1 en GGUF — scale ignorado en v1. v1 trataba los bytes int8 como float32 directos sin aplicar el scale d. v2 extrae d correctamente y devuelve d * qs.
RoPE — convencion incorrecta en v1. La segunda mitad usaba x1sin + x2cos en lugar de la convencion LLaMA canonica [-x2, x1]. Corregido en v2.
SwiGLU — usaba sigmoid(gate) en lugar de silu(gate). Silenciosamente incorrecto. silu(x) = x * sigmoid(x), no sigmoid(x).
conv2d / depthwise_conv2d — formato de pesos Keras vs PyTorch. v1 asumia (kH, kW, C_in, C_out). v2 usa formato PyTorch (C_out, C_in, kH, kW).
H5Loader — cargaba todos los arrays en el constructor (eager). Para un MobileNet eran ~100MB en RAM antes de pedir nada. v2 es lazy.
NpyLoader — cargaba el .npz completo en RAM en el constructor. v2 usa mmap_mode="r" — los arrays se leen del disco solo cuando se piden.
Exporter: se corrige defaul del archivo shora detcta mejor todo
Dependencias
numpy >= 1.24 pyfive (para archivos .h5 / .keras)
Parte del ecosistema UFM
micronnx es la capa de recoleccion de UFM (Unified Fusion Model). Su unica responsabilidad es exponer pesos y activaciones en una estructura uniforme. La logica de fusion, compatibilidad y ajuste fino vive en UFM, no aqui.
Licencia
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file micronnx-0.1.2.3.tar.gz.
File metadata
- Download URL: micronnx-0.1.2.3.tar.gz
- Upload date:
- Size: 86.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: python-requests/2.33.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2e62c1f5ebb2ebb70a0f3d0dc64654bdab64617f8857b8ada95e6859decbcfa
|
|
| MD5 |
d6c47196f588928e74182504f5d71b9c
|
|
| BLAKE2b-256 |
518b44f679b0f128736a73ca05d7f04907fe144ac3fbb678a24d132b03c738e7
|
File details
Details for the file micronnx-0.1.2.3-py3-none-any.whl.
File metadata
- Download URL: micronnx-0.1.2.3-py3-none-any.whl
- Upload date:
- Size: 87.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: python-requests/2.33.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
042dafc0b483e1140391d7be161af3aa7bb95aea65ec7a6e88e2eaa5ee2b74b5
|
|
| MD5 |
b57184fbe1b5e1ed6d82bdaa0e546fff
|
|
| BLAKE2b-256 |
b34d98f73128b90b60e5253facf9f0046431fce3996e86b0ff8c6bc2ba919406
|