Download README.md from imbcmdth/faceage-onnx: direct link, hf CLI and curl.
- Browser
- Download file 7.47 kB
-
https://huggingface.co/imbcmdth/faceage-onnx/resolve/main/README.md
- Command line
-
hf download hf://imbcmdth/faceage-onnx/README.md
-
curl -L -o README.md https://huggingface.co/imbcmdth/faceage-onnx/resolve/main/README.md
license: apache-2.0
base_model: TrungTran/faceage_ClientScan
tags:
- age-estimation
- face-analysis
- onnx
- fp16
- dinov3
faceage-onnx
FaceAge ClientScan halved to fp16 for use as an ffrwd model pin. The network and weights are TrungTran's, Apache-2.0: a DINOv3 ViT-L/16 backbone with a CORAL ordinal-regression age head and a gender head, trained on face crops. This repo holds two converted files of that network and the source's own configuration, nothing more.
Built with DINOv3. The backbone is Meta's, under the DINOv3 License, which travels with any derivative: a copy is in this repo, and anything that redistributes the model carries it too.
| file | bytes | sha256 |
|---|---|---|
| faceage-dino-fp16.onnx | 611872451 | b2e804783e197066cd8cf65a7cbc39a0579ea87c34660abd8ca942df8672cf70 |
| faceage-dino-vitl16-fused-fp16.onnx | 611187743 | 30c5531f17a1faa1f27891cb8af3e12f1fb14ea5f456192d0f1afcc8c9105ba0 |
faceage-dino-fp16.onnx is the source converted to fp16 (see "The
fp16 conversion"). faceage-dino-vitl16-fused-fp16.onnx is the same
network and weights rebuilt with fused operators, 2.6 to 3.9 times as
fast on ONNX Runtime's CUDA provider (see "The fused rebuild"). Both
take the same input and return the same outputs.
The source it was converted from, as downloaded:
| file | bytes | sha256 |
|---|---|---|
| faceage_dino_fp32.onnx | 1219158136 | b0fefb4473978dae8128803d4577de2586f974198991f2c6252a5609dffb3233 |
source/ holds the source repo's config.json,
preprocessor_config.json, modeling_faceage_dino.py and
configuration_faceage_dino.py, which are where the preprocessing and
the decoding below come from.
Model I/O
The input face is Bx3x224x224 fp32, the batch dimension dynamic:
one face crop per row, RGB in NCHW, resized to 224x224 with bicubic
resampling and no aspect padding, scaled to 0..1, then normalized
with ImageNet's mean (0.485, 0.456, 0.406) and standard deviation
(0.229, 0.224, 0.225).
Crop the face with 10 percent proportional padding on each side: for
a box x0, y0, x1, y1, widen it by 0.1 * (x1 - x0) left and right
and by 0.1 * (y1 - y0) above and below, clamped to the image. That
is how the model was trained, and the source card reports that
without the padding its error grows from 3.56 to 3.76 years.
Two outputs, both fp32:
age_logits,Bx100: the CORAL thresholds. The age in years is the sum of their sigmoids,age = sum(sigmoid(age_logits[k]) for k in 0..99), a continuous value in 0..100.gender_logits,Bx2: softmax, then argmax; 0 is female, 1 is male.
The source card reports MAE 3.56 years on LAGENDA (84k faces), with 1.55 on ages 0 to 12 and 2.85 on 13 to 17.
The fp16 conversion
The file is the source graph with every MatMul weight in fp16 and the input and outputs left fp32, so a caller feeds and reads fp32 tensors exactly as it would the source. The weights in fp16 are 99.5 percent of the 304M parameters, which is why the file is half the size.
A plain onnxconverter_common.float16.convert_float_to_float16 with
keep_io_types=True produces a graph that returns NaN for every
input. DINOv3 carries a "massive activation" on its residual stream,
one channel at about 1.57e5 from the first block onward, and fp16
tops out at 65,504. Keeping the residual ops in fp32 through
op_block_list is not enough on its own, because the converter wraps
every blocked node with its own down-cast and up-cast, so the value
still passes through fp16 between two adjacent fp32 nodes and
overflows there.
So the conversion is two steps, both in scripts/:
to_fp16.py LayerNormalization Softmax Add Mulconverts with those four ops added to the converter's default block list.fuse_casts.pyremoves everyCast(to fp16)feeding aCast(to fp32), rewiring the consumer to the original fp32 tensor, and drops the down-casts left without a reader. On this graph that removes 118 up-casts and 47 orphaned down-casts. The residual stream, all 50 LayerNormalizations and all 24 Softmaxes are then fp32 end to end, and the 192 MatMuls stay fp16.
scripts/validate_fp16.py compares the result against the source on
200 WIDER FACE validation face crops of at least 32 px, padded and
resized as above:
| mean absolute age difference | 0.012 years |
| max absolute age difference | 0.074 years |
| NaN or inf in either output | none |
| gender agreement | 200 of 200 |
Converted with onnx 1.22.0, onnxconverter-common 1.16.0 and
onnxruntime 1.29.0 under Python 3.11; opset 18, IR 10, a single
self-contained file with no external data.
The fused rebuild
faceage-dino-vitl16-fused-fp16.onnx is written by
scripts/rebuild_fused.py, which reads the weights out of the source
fp32 file and writes a new graph for them by hand. The network is the
source's; only the way the graph is written changes:
- The residual stream is carried scaled by 1/8, and each LayerNorm's epsilon by 1/64, so every LayerNorm's output is what it was. The massive activation on channel 757 then peaks at about 1.95e4 instead of 1.56e5, inside fp16's 65,504, and the residual stream can no longer overflow. The layer scales, times 1/8, are folded into the output projections' weights and biases.
- Attention is one
com.microsoftMultiHeadAttention per block, in fp16. The rotary position embedding iscom.microsoftRotaryEmbedding, with its cos and sin tables computed once in float64 from the source's angle table and stored as fp16 constants; the CLS token and the four register tokens get rows of cos 1 and sin 0, so one operator covers all 201 tokens. Each residual add and the LayerNorm after it are onecom.microsoftSkipLayerNormalization in fp32, and the MLP's bias and GELU are onecom.microsoftBiasGelu. - v's bias is folded into o's bias. A row of attention weights sums to one, so a constant added to every value passes through attention unchanged.
- The last block computes the query and the MLP for the CLS token only, since only the CLS token is read after it.
- The MatMuls are fp16; the residual stream, the LayerNorms, the input and the outputs are fp32. Every Reshape takes a constant shape with the batch as -1, so no shape arithmetic runs on the host. The graph has 444 nodes, 96 of them Casts.
Compared with the source fp32 on the same 200 WIDER FACE crops as above:
| mean absolute age difference, CPU provider | 0.0036 years |
| mean absolute age difference, CUDA provider | 0.0086 years |
On an RTX 4090 with ONNX Runtime 1.22's CUDA provider, a batch of 1, 5
and 10 faces takes 3.4, 6.8 and 12.1 ms, against 13.2, 19.8 and 31.6
ms for faceage-dino-fp16.onnx.
The com.microsoft operators need ONNX Runtime 1.22 or later. The file
uses opset 18 and com.microsoft opset 1, IR 10, in a single file with
no external data. faceage-dino-fp16.onnx stays in this repo, unchanged,
for the model pins that name it.
License
The weights are Apache-2.0 as the source declares. The backbone is DINOv3, and the DINOv3 License applies to it and to this derivative: use, modification and redistribution are permitted, commercial use included, on condition that the license accompanies any redistribution and that "Built with DINOv3" is displayed where the model is used. The source's training data is its own and is not distributed here.