eml-stdlib 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eml_stdlib/__init__.py +81 -0
- eml_stdlib/ballistics/air_density.eml +73 -0
- eml_stdlib/ballistics/ballistic_solver.eml +102 -0
- eml_stdlib/ballistics/coriolis.eml +60 -0
- eml_stdlib/ballistics/drag.eml +44 -0
- eml_stdlib/ballistics/gravity.eml +52 -0
- eml_stdlib/ballistics/muzzle_velocity.eml +42 -0
- eml_stdlib/ballistics/spin_drift.eml +60 -0
- eml_stdlib/ballistics/time_of_flight.eml +79 -0
- eml_stdlib/ballistics/wind.eml +58 -0
- eml_stdlib/biology/abilities/ballistic_armor.eml +61 -0
- eml_stdlib/biology/abilities/bite_force.eml +48 -0
- eml_stdlib/biology/abilities/chromatophore.eml +50 -0
- eml_stdlib/biology/abilities/discharge.eml +45 -0
- eml_stdlib/biology/abilities/elastic_energy.eml +42 -0
- eml_stdlib/biology/abilities/locomotion.eml +50 -0
- eml_stdlib/biology/abilities/muscle_power.eml +47 -0
- eml_stdlib/biology/abilities/sprint.eml +36 -0
- eml_stdlib/biology/abilities/texture_match.eml +49 -0
- eml_stdlib/biology/abilities/toxin.eml +44 -0
- eml_stdlib/biology/cone_l.eml +36 -0
- eml_stdlib/biology/cone_s.eml +32 -0
- eml_stdlib/biology/cryptochrome.eml +44 -0
- eml_stdlib/biology/geomagnetic_field.eml +32 -0
- eml_stdlib/biology/goldman_equation.eml +41 -0
- eml_stdlib/biology/hill_equation.eml +65 -0
- eml_stdlib/biology/hodgkin_huxley.eml +108 -0
- eml_stdlib/biology/michaelis_menten.eml +65 -0
- eml_stdlib/biology/plume_diffusion.eml +47 -0
- eml_stdlib/biology/receptor_binding.eml +35 -0
- eml_stdlib/biology/rod_sensitivity.eml +38 -0
- eml_stdlib/biology/tapetum.eml +70 -0
- eml_stdlib/carriers/acoustic_cloak.eml +56 -0
- eml_stdlib/carriers/ferron_emission.eml +55 -0
- eml_stdlib/carriers/ferron_logic.eml +45 -0
- eml_stdlib/carriers/ferron_propagation.eml +69 -0
- eml_stdlib/carriers/mach_zehnder.eml +52 -0
- eml_stdlib/carriers/magnon_dispersion.eml +57 -0
- eml_stdlib/carriers/magnon_logic.eml +45 -0
- eml_stdlib/carriers/optical_neuron.eml +55 -0
- eml_stdlib/carriers/phonon_bandgap.eml +54 -0
- eml_stdlib/carriers/ring_resonator.eml +48 -0
- eml_stdlib/carriers/spin_torque.eml +61 -0
- eml_stdlib/carriers/thermal_rectifier.eml +63 -0
- eml_stdlib/catalog.json +4209 -0
- eml_stdlib/circuits/buck_converter.eml +74 -0
- eml_stdlib/circuits/cmos_inverter.eml +75 -0
- eml_stdlib/circuits/h_bridge.eml +69 -0
- eml_stdlib/circuits/ldo.eml +66 -0
- eml_stdlib/circuits/mosfet_iv.eml +59 -0
- eml_stdlib/circuits/pll_loop.eml +63 -0
- eml_stdlib/circuits/rc_filter.eml +98 -0
- eml_stdlib/circuits/voltage_divider.eml +68 -0
- eml_stdlib/control/bangbang.eml +34 -0
- eml_stdlib/control/deadband.eml +28 -0
- eml_stdlib/control/hysteresis.eml +28 -0
- eml_stdlib/control/kalman_1d.eml +53 -0
- eml_stdlib/control/lqr_1d.eml +47 -0
- eml_stdlib/control/pid.eml +38 -0
- eml_stdlib/gaming/animation/bounce.eml +39 -0
- eml_stdlib/gaming/animation/ease_in.eml +27 -0
- eml_stdlib/gaming/animation/ease_in_out.eml +29 -0
- eml_stdlib/gaming/animation/ease_out.eml +27 -0
- eml_stdlib/gaming/animation/ik_2bone.eml +57 -0
- eml_stdlib/gaming/animation/spring.eml +39 -0
- eml_stdlib/gaming/audio/distance_attenuation.eml +50 -0
- eml_stdlib/gaming/audio/doppler.eml +39 -0
- eml_stdlib/gaming/audio/lowpass_audio.eml +32 -0
- eml_stdlib/gaming/audio/reverb_delay.eml +41 -0
- eml_stdlib/gaming/camera/dof_blur.eml +30 -0
- eml_stdlib/gaming/camera/fov_projection.eml +47 -0
- eml_stdlib/gaming/camera/orbit.eml +48 -0
- eml_stdlib/gaming/camera/shake.eml +20 -0
- eml_stdlib/gaming/lighting/ambient_occlusion.eml +40 -0
- eml_stdlib/gaming/lighting/area_light.eml +50 -0
- eml_stdlib/gaming/lighting/fog_exp.eml +28 -0
- eml_stdlib/gaming/lighting/fog_exp2.eml +30 -0
- eml_stdlib/gaming/lighting/point_light.eml +33 -0
- eml_stdlib/gaming/lighting/spot_light.eml +43 -0
- eml_stdlib/gaming/noise/fbm.eml +65 -0
- eml_stdlib/gaming/noise/perlin_2d.eml +65 -0
- eml_stdlib/gaming/noise/perlin_3d.eml +63 -0
- eml_stdlib/gaming/noise/simplex_2d.eml +67 -0
- eml_stdlib/gaming/noise/turbulence.eml +46 -0
- eml_stdlib/gaming/noise/value_noise.eml +45 -0
- eml_stdlib/gaming/noise/voronoi_2d.eml +83 -0
- eml_stdlib/gaming/noise/white_noise.eml +47 -0
- eml_stdlib/gaming/noise/worley.eml +79 -0
- eml_stdlib/gaming/particles/drag.eml +30 -0
- eml_stdlib/gaming/particles/emitter_radial.eml +35 -0
- eml_stdlib/gaming/particles/fade.eml +36 -0
- eml_stdlib/gaming/particles/gravity_particle.eml +32 -0
- eml_stdlib/gaming/particles/size_over_life.eml +28 -0
- eml_stdlib/gaming/physics/aabb_overlap.eml +38 -0
- eml_stdlib/gaming/physics/buoyancy.eml +43 -0
- eml_stdlib/gaming/physics/circle_collision.eml +46 -0
- eml_stdlib/gaming/physics/impulse_resolve.eml +39 -0
- eml_stdlib/gaming/physics/rigid_body_2d.eml +61 -0
- eml_stdlib/gaming/physics/spring_damper.eml +30 -0
- eml_stdlib/gaming/physics/verlet.eml +29 -0
- eml_stdlib/gaming/shading/fresnel.eml +57 -0
- eml_stdlib/gaming/shading/matcap.eml +34 -0
- eml_stdlib/gaming/shading/pbr_diffuse.eml +41 -0
- eml_stdlib/gaming/shading/pbr_specular.eml +75 -0
- eml_stdlib/gaming/shading/subsurface.eml +52 -0
- eml_stdlib/gaming/shading/toon.eml +30 -0
- eml_stdlib/gaming/terrain/biome_select.eml +41 -0
- eml_stdlib/gaming/terrain/cliff_detect.eml +35 -0
- eml_stdlib/gaming/terrain/erosion_hydraulic.eml +57 -0
- eml_stdlib/gaming/terrain/erosion_thermal.eml +44 -0
- eml_stdlib/gaming/terrain/heightmap.eml +33 -0
- eml_stdlib/gaming/textures/brick.eml +27 -0
- eml_stdlib/gaming/textures/caustics.eml +25 -0
- eml_stdlib/gaming/textures/checkerboard.eml +27 -0
- eml_stdlib/gaming/textures/fire.eml +26 -0
- eml_stdlib/gaming/textures/marble.eml +24 -0
- eml_stdlib/gaming/textures/rust.eml +23 -0
- eml_stdlib/gaming/textures/water_surface.eml +49 -0
- eml_stdlib/gaming/textures/wood.eml +41 -0
- eml_stdlib/math/abs.eml +45 -0
- eml_stdlib/math/atan.eml +32 -0
- eml_stdlib/math/atan2.eml +61 -0
- eml_stdlib/math/bessel_j0.eml +65 -0
- eml_stdlib/math/cos.eml +42 -0
- eml_stdlib/math/cosh.eml +24 -0
- eml_stdlib/math/erf.eml +66 -0
- eml_stdlib/math/exp.eml +49 -0
- eml_stdlib/math/gamma.eml +55 -0
- eml_stdlib/math/gaussian.eml +18 -0
- eml_stdlib/math/log.eml +59 -0
- eml_stdlib/math/pow.eml +51 -0
- eml_stdlib/math/relu.eml +63 -0
- eml_stdlib/math/sigmoid.eml +21 -0
- eml_stdlib/math/sin.eml +37 -0
- eml_stdlib/math/sinh.eml +24 -0
- eml_stdlib/math/softmax.eml +87 -0
- eml_stdlib/math/sqrt.eml +46 -0
- eml_stdlib/math/tan.eml +36 -0
- eml_stdlib/math/tanh.eml +54 -0
- eml_stdlib/ml/attention.eml +70 -0
- eml_stdlib/ml/cross_entropy.eml +85 -0
- eml_stdlib/ml/gelu.eml +51 -0
- eml_stdlib/ml/layernorm.eml +49 -0
- eml_stdlib/ml/leaky_relu.eml +51 -0
- eml_stdlib/ml/relu.eml +63 -0
- eml_stdlib/ml/rotary_embed.eml +64 -0
- eml_stdlib/ml/silu.eml +40 -0
- eml_stdlib/ml/softmax.eml +87 -0
- eml_stdlib/physics/coulomb.eml +48 -0
- eml_stdlib/physics/damped_oscillator.eml +20 -0
- eml_stdlib/physics/diffusion.eml +45 -0
- eml_stdlib/physics/gravity.eml +60 -0
- eml_stdlib/physics/hooke.eml +56 -0
- eml_stdlib/physics/navier_stokes_1d.eml +57 -0
- eml_stdlib/physics/planck_radiation.eml +77 -0
- eml_stdlib/physics/stefan_boltzmann.eml +75 -0
- eml_stdlib/physics/wave_propagation.eml +52 -0
- eml_stdlib/quantum/cnot.eml +72 -0
- eml_stdlib/quantum/grover_oracle.eml +69 -0
- eml_stdlib/quantum/hadamard.eml +73 -0
- eml_stdlib/quantum/pauli_x.eml +29 -0
- eml_stdlib/quantum/pauli_z.eml +29 -0
- eml_stdlib/quantum/phase_gate.eml +76 -0
- eml_stdlib/quantum/rotation_rz.eml +92 -0
- eml_stdlib/sensors/accelerometer.eml +59 -0
- eml_stdlib/sensors/hall_effect.eml +42 -0
- eml_stdlib/sensors/photodetector.eml +58 -0
- eml_stdlib/sensors/rtd.eml +57 -0
- eml_stdlib/sensors/strain_gauge.eml +63 -0
- eml_stdlib/sensors/thermistor.eml +71 -0
- eml_stdlib/signal/bandpass.eml +39 -0
- eml_stdlib/signal/chirp.eml +42 -0
- eml_stdlib/signal/dft_single.eml +30 -0
- eml_stdlib/signal/fft_butterfly.eml +36 -0
- eml_stdlib/signal/highpass.eml +45 -0
- eml_stdlib/signal/lowpass.eml +49 -0
- eml_stdlib/signal/matched_filter.eml +48 -0
- eml_stdlib/signal/notch_filter.eml +47 -0
- eml_stdlib/signal/window_hamming.eml +47 -0
- eml_stdlib/signal/window_hann.eml +50 -0
- eml_stdlib-0.4.0.dist-info/METADATA +453 -0
- eml_stdlib-0.4.0.dist-info/RECORD +185 -0
- eml_stdlib-0.4.0.dist-info/WHEEL +5 -0
- eml_stdlib-0.4.0.dist-info/licenses/LICENSE +21 -0
- eml_stdlib-0.4.0.dist-info/top_level.txt +1 -0
eml_stdlib/math/sin.eml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// sin.eml -- sine, with an explicit chain-order-1 contract.
|
|
2
|
+
//
|
|
3
|
+
// Pythagorean witness function included so downstream proofs of
|
|
4
|
+
// energy conservation / unitarity can inline it directly.
|
|
5
|
+
|
|
6
|
+
module sin_module;
|
|
7
|
+
|
|
8
|
+
const PI: Real = 3.141592653589793
|
|
9
|
+
const TWO_PI: Real = 6.283185307179586
|
|
10
|
+
|
|
11
|
+
@verify(lean, theorem = "sin_in_unit_interval")
|
|
12
|
+
fn sin_kernel(x: Real) -> Real
|
|
13
|
+
where chain_order <= 1
|
|
14
|
+
ensures (result >= -1.0)
|
|
15
|
+
ensures (result <= 1.0)
|
|
16
|
+
{
|
|
17
|
+
sin(x)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@verify(lean, theorem = "sin_zero_is_zero")
|
|
21
|
+
fn sin_at_zero() -> Real
|
|
22
|
+
where chain_order <= 0
|
|
23
|
+
ensures (result == 0.0)
|
|
24
|
+
{
|
|
25
|
+
sin(0.0)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Squared sine -- chain order 1 (the chain budget folds in the
|
|
29
|
+
// double-angle identity). Used by Mach-Zehnder / coupler power
|
|
30
|
+
// ratios so it gets a name + a Lean obligation of its own.
|
|
31
|
+
@verify(lean, theorem = "sin_sq_nonneg")
|
|
32
|
+
fn sin_sq(x: Real) -> Real
|
|
33
|
+
where chain_order <= 1
|
|
34
|
+
ensures (result >= 0.0)
|
|
35
|
+
{
|
|
36
|
+
sin(x) * sin(x)
|
|
37
|
+
}
|
eml_stdlib/math/sinh.eml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// sinh.eml -- hyperbolic sine.
|
|
2
|
+
//
|
|
3
|
+
// sinh(x) = (e^x - e^-x) / 2. Chain order 1 -- the two exps fold
|
|
4
|
+
// through one shared evaluator on most backends.
|
|
5
|
+
|
|
6
|
+
module sinh_module;
|
|
7
|
+
|
|
8
|
+
const SINH_X_MAX: Real = 30.0
|
|
9
|
+
|
|
10
|
+
@verify(lean, theorem = "sinh_zero_is_zero")
|
|
11
|
+
fn sinh_kernel(x: Real) -> Real
|
|
12
|
+
where chain_order <= 1
|
|
13
|
+
requires (abs(x) < SINH_X_MAX)
|
|
14
|
+
{
|
|
15
|
+
sinh(x)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@verify(lean, theorem = "sinh_zero_is_zero_at_zero")
|
|
19
|
+
fn sinh_at_zero() -> Real
|
|
20
|
+
where chain_order <= 0
|
|
21
|
+
ensures (result == 0.0)
|
|
22
|
+
{
|
|
23
|
+
sinh(0.0)
|
|
24
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// softmax.eml -- numerically-stable softmax kernels.
|
|
2
|
+
//
|
|
3
|
+
// Softmax over an N-vector requires (a) subtracting the max for
|
|
4
|
+
// numerical stability and (b) reducing across the vector. EML's
|
|
5
|
+
// scalar-kernel surface naturally splits this into three pure
|
|
6
|
+
// gates that the host loop composes:
|
|
7
|
+
//
|
|
8
|
+
// softmax_shift_exp(logit, max_logit) per-element exp(x - m)
|
|
9
|
+
// softmax_normalize(numer, denom_sum) per-element / sum
|
|
10
|
+
// softmax_two_logit(a, b) closed form for N=2
|
|
11
|
+
//
|
|
12
|
+
// The FPGA allocator (Patent #14) maps the per-element gates to
|
|
13
|
+
// SIMD lanes and the reduction to a tree adder; cross-target
|
|
14
|
+
// equivalence (Patent #22) then guarantees the C / Rust / Lean
|
|
15
|
+
// reference matches the Verilog bit-for-bit at IEEE-754
|
|
16
|
+
// rounding.
|
|
17
|
+
|
|
18
|
+
module softmax;
|
|
19
|
+
|
|
20
|
+
const SOFTMAX_X_MAX: Real = 30.0
|
|
21
|
+
|
|
22
|
+
type SoftmaxNumerator = Real where chain_order <= 1
|
|
23
|
+
type SoftmaxValue = Real where chain_order <= 1
|
|
24
|
+
|
|
25
|
+
// Per-element exp(x - max) -- the numerically-stable softmax
|
|
26
|
+
// numerator. `max_logit` is the running maximum of the logit
|
|
27
|
+
// vector, supplied by the host (computed in a separate reduce).
|
|
28
|
+
// Subtracting it before exp() guarantees the largest numerator
|
|
29
|
+
// equals 1.0 and the smallest doesn't underflow.
|
|
30
|
+
@target(fpga, clock_mhz = 250, precision = float32)
|
|
31
|
+
@verify(lean, theorem = "softmax_numerator_bounded_by_one")
|
|
32
|
+
fn softmax_shift_exp(logit: Real, max_logit: Real) -> SoftmaxNumerator
|
|
33
|
+
where chain_order <= 1
|
|
34
|
+
requires (abs(logit) < SOFTMAX_X_MAX)
|
|
35
|
+
requires (abs(max_logit) < SOFTMAX_X_MAX)
|
|
36
|
+
requires (logit <= max_logit)
|
|
37
|
+
ensures (result >= 0.0)
|
|
38
|
+
ensures (result <= 1.0)
|
|
39
|
+
{
|
|
40
|
+
exp(logit - max_logit)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Per-element normaliser. `denom_sum` is the sum of the shifted
|
|
44
|
+
// numerators; supplied by the host's reduction tree.
|
|
45
|
+
@target(fpga, clock_mhz = 400, precision = float32)
|
|
46
|
+
@verify(lean, theorem = "softmax_value_in_unit_interval")
|
|
47
|
+
fn softmax_normalize(numer: Real, denom_sum: Real) -> SoftmaxValue
|
|
48
|
+
where chain_order <= 0
|
|
49
|
+
requires (numer >= 0.0)
|
|
50
|
+
requires (denom_sum > 0.0)
|
|
51
|
+
requires (numer <= denom_sum)
|
|
52
|
+
ensures (result >= 0.0)
|
|
53
|
+
ensures (result <= 1.0)
|
|
54
|
+
{
|
|
55
|
+
numer / denom_sum
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Closed-form softmax for N=2 -- the binary-classifier head.
|
|
59
|
+
// Equivalent to sigmoid(a - b); useful as a sanity-check
|
|
60
|
+
// for the sigmoid-vs-softmax-for-binary debate. Chain order 1.
|
|
61
|
+
@target(fpga, clock_mhz = 250, precision = float32)
|
|
62
|
+
@verify(lean, theorem = "softmax_two_equals_sigmoid_diff")
|
|
63
|
+
fn softmax_two_logit(a: Real, b: Real) -> SoftmaxValue
|
|
64
|
+
where chain_order <= 1
|
|
65
|
+
requires (abs(a) < SOFTMAX_X_MAX)
|
|
66
|
+
requires (abs(b) < SOFTMAX_X_MAX)
|
|
67
|
+
ensures (result >= 0.0)
|
|
68
|
+
ensures (result <= 1.0)
|
|
69
|
+
{
|
|
70
|
+
1.0 / (1.0 + exp(b - a))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Log-sum-exp (LSE) -- stable computation of log(sum exp(x_i)).
|
|
74
|
+
// The host loop accumulates `running_lse` and `next_logit`
|
|
75
|
+
// pair-wise via this gate. Chain order 2 (ln composes through
|
|
76
|
+
// exp). Used as the cross-entropy denominator in transformer
|
|
77
|
+
// language-model heads.
|
|
78
|
+
@target(fpga, clock_mhz = 200, precision = float32)
|
|
79
|
+
@verify(lean, theorem = "log_sum_exp_pair_associative")
|
|
80
|
+
fn log_sum_exp_pair(a: Real, b: Real) -> Real
|
|
81
|
+
where chain_order <= 2
|
|
82
|
+
requires (abs(a) < SOFTMAX_X_MAX)
|
|
83
|
+
requires (abs(b) < SOFTMAX_X_MAX)
|
|
84
|
+
{
|
|
85
|
+
let m = clamp(a, b, SOFTMAX_X_MAX);
|
|
86
|
+
m + ln(exp(a - m) + exp(b - m))
|
|
87
|
+
}
|
eml_stdlib/math/sqrt.eml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// sqrt.eml -- square root + 2D/3D Euclidean distance.
|
|
2
|
+
//
|
|
3
|
+
// `sqrt` is a Forge builtin. This module ships sqrt with an
|
|
4
|
+
// explicit positivity contract plus the two most-used compositions
|
|
5
|
+
// (Euclidean distance) so callers can drop them into kernels
|
|
6
|
+
// without re-deriving the chain-order budget.
|
|
7
|
+
|
|
8
|
+
module sqrt_module;
|
|
9
|
+
|
|
10
|
+
@verify(lean, theorem = "sqrt_nonneg")
|
|
11
|
+
fn sqrt_kernel(x: Real) -> Real
|
|
12
|
+
where chain_order <= 1,
|
|
13
|
+
domain: x >= 0.0
|
|
14
|
+
ensures (result >= 0.0)
|
|
15
|
+
{
|
|
16
|
+
sqrt(x)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2D Euclidean distance.
|
|
20
|
+
@verify(lean, theorem = "hypot2_nonneg")
|
|
21
|
+
fn hypot2(x: Real, y: Real) -> Real
|
|
22
|
+
where chain_order <= 1
|
|
23
|
+
ensures (result >= 0.0)
|
|
24
|
+
{
|
|
25
|
+
sqrt(x * x + y * y)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 3D Euclidean distance.
|
|
29
|
+
@verify(lean, theorem = "hypot3_nonneg")
|
|
30
|
+
fn hypot3(x: Real, y: Real, z: Real) -> Real
|
|
31
|
+
where chain_order <= 1
|
|
32
|
+
ensures (result >= 0.0)
|
|
33
|
+
{
|
|
34
|
+
sqrt(x * x + y * y + z * z)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Inverse square root -- shows up in normal-vector renormalisation,
|
|
38
|
+
// 1/sqrt(2pi) Gaussian prefactors, and 1/r^2 force laws.
|
|
39
|
+
@verify(lean, theorem = "rsqrt_positive")
|
|
40
|
+
fn rsqrt(x: Real) -> Real
|
|
41
|
+
where chain_order <= 1,
|
|
42
|
+
domain: x > 0.0
|
|
43
|
+
ensures (result > 0.0)
|
|
44
|
+
{
|
|
45
|
+
1.0 / sqrt(x)
|
|
46
|
+
}
|
eml_stdlib/math/tan.eml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// tan.eml -- tangent.
|
|
2
|
+
//
|
|
3
|
+
// Chain order 2: tan = sin / cos -- one transcendental in the
|
|
4
|
+
// numerator and one in the denominator. Caller is responsible for
|
|
5
|
+
// keeping x away from (k + 1/2) * pi where cos vanishes.
|
|
6
|
+
|
|
7
|
+
module tan_module;
|
|
8
|
+
|
|
9
|
+
const PI: Real = 3.141592653589793
|
|
10
|
+
const HALF_PI: Real = 1.5707963267948966
|
|
11
|
+
const SAFE_MARGIN: Real = 0.01
|
|
12
|
+
|
|
13
|
+
@verify(lean, theorem = "tan_zero_is_zero")
|
|
14
|
+
fn tan_kernel(x: Real) -> Real
|
|
15
|
+
where chain_order <= 2,
|
|
16
|
+
domain: abs(cos(x)) > SAFE_MARGIN
|
|
17
|
+
{
|
|
18
|
+
tan(x)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@verify(lean, theorem = "tan_at_zero_is_zero")
|
|
22
|
+
fn tan_at_zero() -> Real
|
|
23
|
+
where chain_order <= 0
|
|
24
|
+
ensures (result == 0.0)
|
|
25
|
+
{
|
|
26
|
+
tan(0.0)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Double-tangent identity numerator: 2*tan(x) -- shows up enough
|
|
30
|
+
// in trig-rationalisation passes to get its own kernel.
|
|
31
|
+
fn two_tan(x: Real) -> Real
|
|
32
|
+
where chain_order <= 2,
|
|
33
|
+
domain: abs(cos(x)) > SAFE_MARGIN
|
|
34
|
+
{
|
|
35
|
+
2.0 * tan(x)
|
|
36
|
+
}
|
eml_stdlib/math/tanh.eml
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// tanh.eml -- hyperbolic-tangent activation.
|
|
2
|
+
//
|
|
3
|
+
// Canonical bipolar squashing function. Range (-1, 1), zero
|
|
4
|
+
// mean for symmetric input distributions, smooth and analytic
|
|
5
|
+
// everywhere. Chain order 1 (single transcendental).
|
|
6
|
+
//
|
|
7
|
+
// We also expose `hard_tanh` (a piecewise-linear
|
|
8
|
+
// FPGA-friendly approximation) and `tanh_from_sigmoid` -- the
|
|
9
|
+
// 2*sigmoid(2x) - 1 identity that SuperBEST canonicalises to a
|
|
10
|
+
// single tanh call (saving one extra exp under the hood).
|
|
11
|
+
|
|
12
|
+
module tanh_activation;
|
|
13
|
+
|
|
14
|
+
const TANH_X_MAX: Real = 30.0
|
|
15
|
+
|
|
16
|
+
type BipolarSignal = Real where chain_order <= 1
|
|
17
|
+
|
|
18
|
+
@target(fpga, clock_mhz = 250, precision = float32)
|
|
19
|
+
@verify(lean, theorem = "tanh_monotone_in_x")
|
|
20
|
+
fn tanh_activation(x: Real) -> BipolarSignal
|
|
21
|
+
where chain_order <= 1
|
|
22
|
+
requires (abs(x) < TANH_X_MAX)
|
|
23
|
+
ensures (result >= -1.0)
|
|
24
|
+
ensures (result <= 1.0)
|
|
25
|
+
{
|
|
26
|
+
tanh(x)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Sigmoid-form identity: tanh(x) = 2*sigmoid(2x) - 1. SuperBEST
|
|
30
|
+
// rewrites this exact pattern back to the single tanh call above,
|
|
31
|
+
// recovering the chain-1 cost despite the surface chain-2 form.
|
|
32
|
+
@target(fpga, clock_mhz = 250, precision = float32)
|
|
33
|
+
@verify(lean, theorem = "tanh_from_sigmoid_equals_canonical")
|
|
34
|
+
fn tanh_from_sigmoid(x: Real) -> BipolarSignal
|
|
35
|
+
where chain_order <= 2
|
|
36
|
+
requires (abs(x) < TANH_X_MAX)
|
|
37
|
+
ensures (result >= -1.0)
|
|
38
|
+
ensures (result <= 1.0)
|
|
39
|
+
{
|
|
40
|
+
2.0 / (1.0 + exp(-2.0 * x)) - 1.0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Hard tanh -- piecewise linear, chain order 0. Used in
|
|
44
|
+
// quantised inference (int8 / int16) where the saturation
|
|
45
|
+
// nonlinearity is a single clamp instead of a transcendental.
|
|
46
|
+
@target(fpga, clock_mhz = 400, precision = float32)
|
|
47
|
+
@verify(lean, theorem = "hard_tanh_bounded")
|
|
48
|
+
fn hard_tanh(x: Real) -> BipolarSignal
|
|
49
|
+
where chain_order <= 0
|
|
50
|
+
ensures (result >= -1.0)
|
|
51
|
+
ensures (result <= 1.0)
|
|
52
|
+
{
|
|
53
|
+
clamp(x, -1.0, 1.0)
|
|
54
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// attention.eml -- scaled dot-product attention kernel.
|
|
2
|
+
//
|
|
3
|
+
// One query-key pair contributes a score:
|
|
4
|
+
//
|
|
5
|
+
// score = (q . k) / sqrt(d_k)
|
|
6
|
+
//
|
|
7
|
+
// The softmax over scores produces the attention weights; the
|
|
8
|
+
// output is the weighted sum of values:
|
|
9
|
+
//
|
|
10
|
+
// out = sum_j softmax(score_j) * v_j
|
|
11
|
+
//
|
|
12
|
+
// EML kernels operate on scalars; the host threads in the dot
|
|
13
|
+
// product, the LSE reduction, and the value sum. This module
|
|
14
|
+
// provides:
|
|
15
|
+
//
|
|
16
|
+
// scale_factor(d_k) -- 1 / sqrt(d_k)
|
|
17
|
+
// score_qk(q_dot_k, scale) -- chain order 0 multiply
|
|
18
|
+
// attention_step(...) -- per-(j) contribution to output
|
|
19
|
+
//
|
|
20
|
+
// Plus the mask kernels for causal / padding masking.
|
|
21
|
+
|
|
22
|
+
module attention;
|
|
23
|
+
|
|
24
|
+
@verify(lean, theorem = "attn_scale_positive")
|
|
25
|
+
fn scale_factor(d_k: Real) -> Real
|
|
26
|
+
where chain_order <= 1,
|
|
27
|
+
domain: d_k > 0.0
|
|
28
|
+
ensures (result > 0.0)
|
|
29
|
+
{
|
|
30
|
+
1.0 / sqrt(d_k)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@verify(lean, theorem = "attn_score_signed")
|
|
34
|
+
fn score_qk(q_dot_k: Real, scale: Real) -> Real
|
|
35
|
+
where chain_order <= 0,
|
|
36
|
+
domain: scale > 0.0
|
|
37
|
+
{
|
|
38
|
+
q_dot_k * scale
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Per-position contribution to the output. softmax_weight is the
|
|
42
|
+
// pre-computed exp(score_j - lse) value from the host. v is the
|
|
43
|
+
// j-th value.
|
|
44
|
+
@verify(lean, theorem = "attn_step_signed")
|
|
45
|
+
fn attention_step(softmax_weight: Real, v: Real) -> Real
|
|
46
|
+
where chain_order <= 0
|
|
47
|
+
requires (softmax_weight >= 0.0)
|
|
48
|
+
requires (softmax_weight <= 1.0)
|
|
49
|
+
{
|
|
50
|
+
softmax_weight * v
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Causal mask: returns -1e30 for masked positions, 0 otherwise.
|
|
54
|
+
// Caller adds this to the score before softmax.
|
|
55
|
+
@verify(lean, theorem = "causal_mask_signed")
|
|
56
|
+
fn causal_mask(query_pos: Real, key_pos: Real) -> Real
|
|
57
|
+
where chain_order <= 0
|
|
58
|
+
{
|
|
59
|
+
let allowed = clamp((query_pos - key_pos) * 1.0e30 + 1.0, 0.0, 1.0);
|
|
60
|
+
(1.0 - allowed) * -1.0e30
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Padding mask -- positions where pad == 1 get -inf.
|
|
64
|
+
@verify(lean, theorem = "pad_mask_signed")
|
|
65
|
+
fn padding_mask(pad: Real) -> Real
|
|
66
|
+
where chain_order <= 0
|
|
67
|
+
requires (pad == 0.0 || pad == 1.0)
|
|
68
|
+
{
|
|
69
|
+
pad * -1.0e30
|
|
70
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// cross_entropy.eml -- cross-entropy loss kernels.
|
|
2
|
+
//
|
|
3
|
+
// The standard classifier loss. For binary targets:
|
|
4
|
+
//
|
|
5
|
+
// bce(p, y) = -(y * ln(p) + (1-y) * ln(1-p))
|
|
6
|
+
//
|
|
7
|
+
// The numerically-stable form is to compute the loss in
|
|
8
|
+
// logit-space (the input to the final sigmoid) instead of
|
|
9
|
+
// probability-space, since ln(sigmoid(z)) collapses to
|
|
10
|
+
// -softplus(-z) which avoids the catastrophic-cancellation
|
|
11
|
+
// region around p ≈ 0 or p ≈ 1.
|
|
12
|
+
//
|
|
13
|
+
// Both forms ship here; SuperBEST's cross_entropy rewrite rule
|
|
14
|
+
// (E-193 calibration) detects the prob-space form and rewrites
|
|
15
|
+
// it to the logit-space form, saving ~2.4 decimal digits at the
|
|
16
|
+
// saturated tails.
|
|
17
|
+
|
|
18
|
+
module cross_entropy;
|
|
19
|
+
|
|
20
|
+
const MAX_LOGIT: Real = 30.0
|
|
21
|
+
const EPS: Real = 1.0e-7
|
|
22
|
+
|
|
23
|
+
type LossSignal = Real where chain_order <= 2
|
|
24
|
+
|
|
25
|
+
// Probability-space binary cross-entropy. Hand-written form;
|
|
26
|
+
// SuperBEST rewrites to the logit-space form below before any
|
|
27
|
+
// backend emits.
|
|
28
|
+
@target(fpga, clock_mhz = 200, precision = float32)
|
|
29
|
+
@verify(lean, theorem = "bce_loss_nonnegative")
|
|
30
|
+
fn bce_loss(prediction: Real, target: Real) -> LossSignal
|
|
31
|
+
where chain_order <= 2,
|
|
32
|
+
domain: 0.0 < prediction && prediction < 1.0
|
|
33
|
+
requires (target >= 0.0)
|
|
34
|
+
requires (target <= 1.0)
|
|
35
|
+
ensures (result >= 0.0)
|
|
36
|
+
{
|
|
37
|
+
-(target * ln(prediction) + (1.0 - target) * ln(1.0 - prediction))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Logit-space (numerically stable) form. Equivalent to
|
|
41
|
+
// max(z, 0) - z * y + ln(1 + exp(-|z|))
|
|
42
|
+
// the form torch.nn.BCEWithLogitsLoss compiles to. Chain order 2
|
|
43
|
+
// (ln composes through exp).
|
|
44
|
+
@target(fpga, clock_mhz = 200, precision = float32)
|
|
45
|
+
@verify(lean, theorem = "bce_with_logits_equals_bce")
|
|
46
|
+
fn bce_with_logits(logit: Real, target: Real) -> LossSignal
|
|
47
|
+
where chain_order <= 2
|
|
48
|
+
requires (abs(logit) < MAX_LOGIT)
|
|
49
|
+
requires (target >= 0.0)
|
|
50
|
+
requires (target <= 1.0)
|
|
51
|
+
ensures (result >= 0.0)
|
|
52
|
+
{
|
|
53
|
+
let pos = clamp(logit, 0.0, MAX_LOGIT);
|
|
54
|
+
pos - logit * target + ln(1.0 + exp(-abs(logit)))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Categorical cross-entropy contribution from one (logit, target)
|
|
58
|
+
// pair, given the host has already supplied log_sum_exp(logits)
|
|
59
|
+
// from the LSE reduction. Chain order 1 (single ln baked into
|
|
60
|
+
// log_sum_exp; this gate is just subtract-and-multiply).
|
|
61
|
+
@target(fpga, clock_mhz = 400, precision = float32)
|
|
62
|
+
@verify(lean, theorem = "categorical_ce_pair_nonnegative")
|
|
63
|
+
fn categorical_ce_pair(logit: Real, log_sum_exp_val: Real, target_one_hot: Real) -> LossSignal
|
|
64
|
+
where chain_order <= 0
|
|
65
|
+
requires (abs(logit) < MAX_LOGIT)
|
|
66
|
+
requires (target_one_hot >= 0.0)
|
|
67
|
+
requires (target_one_hot <= 1.0)
|
|
68
|
+
{
|
|
69
|
+
target_one_hot * (log_sum_exp_val - logit)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Gradient of BCE-with-logits w.r.t. the logit. Equals
|
|
73
|
+
// sigmoid(z) - y
|
|
74
|
+
// SuperBEST recognises -ln(sigmoid(-z))-shaped patterns in the
|
|
75
|
+
// surrounding code and hoists this gradient when the forward
|
|
76
|
+
// pass and backward pass share the same logit.
|
|
77
|
+
@target(fpga, clock_mhz = 250, precision = float32)
|
|
78
|
+
fn bce_with_logits_grad(logit: Real, target: Real) -> LossSignal
|
|
79
|
+
where chain_order <= 1
|
|
80
|
+
requires (abs(logit) < MAX_LOGIT)
|
|
81
|
+
requires (target >= 0.0)
|
|
82
|
+
requires (target <= 1.0)
|
|
83
|
+
{
|
|
84
|
+
1.0 / (1.0 + exp(-logit)) - target
|
|
85
|
+
}
|
eml_stdlib/ml/gelu.eml
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// gelu.eml -- Gaussian Error Linear Unit, the activation BERT
|
|
2
|
+
// and GPT use.
|
|
3
|
+
//
|
|
4
|
+
// The exact GELU is
|
|
5
|
+
//
|
|
6
|
+
// gelu(x) = x * Phi(x)
|
|
7
|
+
//
|
|
8
|
+
// where Phi is the standard normal CDF. Phi has no closed-form
|
|
9
|
+
// elementary expression, so production code uses one of two
|
|
10
|
+
// approximations:
|
|
11
|
+
//
|
|
12
|
+
// gelu_tanh chain 1: 0.5*x*(1 + tanh(sqrt(2/pi) * (x + 0.044715*x^3)))
|
|
13
|
+
// gelu_erf chain 1: 0.5*x*(1 + erf(x / sqrt(2)))
|
|
14
|
+
//
|
|
15
|
+
// erf is a Forge builtin in the f64 path; for f32 the tanh form
|
|
16
|
+
// is the canonical FPGA-friendly choice (one transcendental
|
|
17
|
+
// instead of one + special function).
|
|
18
|
+
//
|
|
19
|
+
// Numeric range: bounded |x| < 30 keeps the cubic term inside
|
|
20
|
+
// float32 dynamic range and lets the Lean monotonicity proof
|
|
21
|
+
// avoid +/-inf edge cases.
|
|
22
|
+
|
|
23
|
+
module gelu;
|
|
24
|
+
|
|
25
|
+
const GELU_X_MAX: Real = 30.0
|
|
26
|
+
const SQRT_2_OVER_PI: Real = 0.7978845608028654
|
|
27
|
+
const GELU_CUBIC: Real = 0.044715
|
|
28
|
+
const HALF: Real = 0.5
|
|
29
|
+
|
|
30
|
+
type GeluSignal = Real where chain_order <= 1
|
|
31
|
+
|
|
32
|
+
@target(fpga, clock_mhz = 200, precision = float32)
|
|
33
|
+
@verify(lean, theorem = "gelu_tanh_monotone_in_x")
|
|
34
|
+
fn gelu_tanh(x: Real) -> GeluSignal
|
|
35
|
+
where chain_order <= 1
|
|
36
|
+
requires (abs(x) < GELU_X_MAX)
|
|
37
|
+
{
|
|
38
|
+
HALF * x * (1.0 + tanh(SQRT_2_OVER_PI * (x + GELU_CUBIC * x * x * x)))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// QuickGELU -- the OpenAI CLIP variant: x * sigmoid(1.702 * x).
|
|
42
|
+
// Cheaper than gelu_tanh (one exp instead of one tanh + cubic);
|
|
43
|
+
// matches gelu_tanh to ~3 decimal digits on N(0,1) inputs.
|
|
44
|
+
@target(fpga, clock_mhz = 250, precision = float32)
|
|
45
|
+
@verify(lean, theorem = "quick_gelu_monotone_in_x")
|
|
46
|
+
fn quick_gelu(x: Real) -> GeluSignal
|
|
47
|
+
where chain_order <= 1
|
|
48
|
+
requires (abs(x) < GELU_X_MAX)
|
|
49
|
+
{
|
|
50
|
+
x / (1.0 + exp(-1.702 * x))
|
|
51
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// layernorm.eml -- per-feature layer normalization kernel.
|
|
2
|
+
//
|
|
3
|
+
// Given a feature vector x, the LayerNorm transform is:
|
|
4
|
+
//
|
|
5
|
+
// mu = mean(x)
|
|
6
|
+
// var = mean((x - mu)^2)
|
|
7
|
+
// x_norm = (x - mu) / sqrt(var + eps)
|
|
8
|
+
// y = gamma * x_norm + beta
|
|
9
|
+
//
|
|
10
|
+
// EML kernels operate per-element -- the host program threads the
|
|
11
|
+
// per-vector statistics in. Chain order 1 (single sqrt). The
|
|
12
|
+
// closed proofs cover unit-variance + zero-mean of `x_norm` in the
|
|
13
|
+
// no-noise limit.
|
|
14
|
+
|
|
15
|
+
module layernorm;
|
|
16
|
+
|
|
17
|
+
const DEFAULT_EPS: Real = 1.0e-5
|
|
18
|
+
|
|
19
|
+
// Normalisation step for a single element.
|
|
20
|
+
@verify(lean, theorem = "layernorm_unit_variance_in_limit")
|
|
21
|
+
fn layernorm_step(x: Real, mu: Real, var: Real, eps: Real,
|
|
22
|
+
gamma: Real, beta: Real) -> Real
|
|
23
|
+
where chain_order <= 1,
|
|
24
|
+
domain: var >= 0.0 && eps > 0.0
|
|
25
|
+
{
|
|
26
|
+
let x_norm = (x - mu) / sqrt(var + eps);
|
|
27
|
+
gamma * x_norm + beta
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Default-eps variant.
|
|
31
|
+
@verify(lean, theorem = "layernorm_default_eps_unit_variance_in_limit")
|
|
32
|
+
fn layernorm_default(x: Real, mu: Real, var: Real,
|
|
33
|
+
gamma: Real, beta: Real) -> Real
|
|
34
|
+
where chain_order <= 1,
|
|
35
|
+
domain: var >= 0.0
|
|
36
|
+
{
|
|
37
|
+
let x_norm = (x - mu) / sqrt(var + DEFAULT_EPS);
|
|
38
|
+
gamma * x_norm + beta
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// RMSNorm variant -- skips the mean subtraction (used in LLaMA).
|
|
42
|
+
// y = x / sqrt(mean(x^2) + eps) * gamma
|
|
43
|
+
@verify(lean, theorem = "rmsnorm_signed_in_input_band")
|
|
44
|
+
fn rmsnorm_step(x: Real, mean_sq: Real, eps: Real, gamma: Real) -> Real
|
|
45
|
+
where chain_order <= 1,
|
|
46
|
+
domain: mean_sq >= 0.0 && eps > 0.0
|
|
47
|
+
{
|
|
48
|
+
gamma * x / sqrt(mean_sq + eps)
|
|
49
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// leaky_relu.eml -- Leaky ReLU activation.
|
|
2
|
+
//
|
|
3
|
+
// leaky_relu(x) = x if x >= 0 else alpha * x
|
|
4
|
+
//
|
|
5
|
+
// Standard alpha = 0.01 prevents the dying-ReLU problem at the
|
|
6
|
+
// cost of a small negative-tail contribution to the gradient.
|
|
7
|
+
// Chain order 0; branch-free via clamp + linear blend.
|
|
8
|
+
|
|
9
|
+
module leaky_relu;
|
|
10
|
+
|
|
11
|
+
const DEFAULT_ALPHA: Real = 0.01
|
|
12
|
+
|
|
13
|
+
@verify(lean, theorem = "leaky_relu_at_zero_is_zero")
|
|
14
|
+
fn leaky_relu(x: Real, alpha: Real) -> Real
|
|
15
|
+
where chain_order <= 0
|
|
16
|
+
requires (alpha >= 0.0)
|
|
17
|
+
{
|
|
18
|
+
let pos = clamp(x, 0.0, 1.0e30);
|
|
19
|
+
let neg = clamp(x, -1.0e30, 0.0);
|
|
20
|
+
pos + alpha * neg
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@verify(lean, theorem = "default_leaky_relu_at_zero_is_zero")
|
|
24
|
+
fn leaky_relu_default(x: Real) -> Real
|
|
25
|
+
where chain_order <= 0
|
|
26
|
+
{
|
|
27
|
+
let pos = clamp(x, 0.0, 1.0e30);
|
|
28
|
+
let neg = clamp(x, -1.0e30, 0.0);
|
|
29
|
+
pos + DEFAULT_ALPHA * neg
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// PReLU: alpha is a learnable parameter (per channel).
|
|
33
|
+
@verify(lean, theorem = "prelu_at_zero_is_zero")
|
|
34
|
+
fn prelu(x: Real, alpha: Real) -> Real
|
|
35
|
+
where chain_order <= 0
|
|
36
|
+
{
|
|
37
|
+
let pos = clamp(x, 0.0, 1.0e30);
|
|
38
|
+
let neg = clamp(x, -1.0e30, 0.0);
|
|
39
|
+
pos + alpha * neg
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Gradient of leaky_relu: 1 for x >= 0, alpha for x < 0.
|
|
43
|
+
@verify(lean, theorem = "leaky_relu_grad_in_alpha_band")
|
|
44
|
+
fn leaky_relu_grad(x: Real, alpha: Real) -> Real
|
|
45
|
+
where chain_order <= 0
|
|
46
|
+
requires (alpha >= 0.0)
|
|
47
|
+
ensures (result >= 0.0)
|
|
48
|
+
{
|
|
49
|
+
let s = clamp(x * 1.0e30, 0.0, 1.0);
|
|
50
|
+
s + alpha * (1.0 - s)
|
|
51
|
+
}
|
eml_stdlib/ml/relu.eml
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// relu.eml -- rectified linear unit family.
|
|
2
|
+
//
|
|
3
|
+
// All four entries in this module are chain order 0 -- ReLU is
|
|
4
|
+
// branch-free in EML because we lower max(0, x) to clamp(x, 0, +inf)
|
|
5
|
+
// which a backend can either emit as a CMOV / select or as a
|
|
6
|
+
// single MAX instruction. No transcendental.
|
|
7
|
+
//
|
|
8
|
+
// On FPGA this is the cheapest activation per layer and the
|
|
9
|
+
// reason quantised inference cores (Patent #14 allocator) bias
|
|
10
|
+
// toward ReLU + variants over sigmoid / tanh.
|
|
11
|
+
|
|
12
|
+
module relu;
|
|
13
|
+
|
|
14
|
+
const RELU_UPPER: Real = 1.0e30
|
|
15
|
+
const ALPHA_LEAKY: Real = 0.01
|
|
16
|
+
|
|
17
|
+
type RectifiedSignal = Real where chain_order <= 0
|
|
18
|
+
|
|
19
|
+
@target(fpga, clock_mhz = 500, precision = float32)
|
|
20
|
+
@verify(lean, theorem = "relu_nonnegative")
|
|
21
|
+
fn relu(x: Real) -> RectifiedSignal
|
|
22
|
+
where chain_order <= 0
|
|
23
|
+
ensures (result >= 0.0)
|
|
24
|
+
ensures (result >= x)
|
|
25
|
+
{
|
|
26
|
+
clamp(x, 0.0, RELU_UPPER)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Leaky ReLU -- non-zero gradient for negative inputs, helps
|
|
30
|
+
// dead-neuron problem. `alpha` is typically 0.01.
|
|
31
|
+
@target(fpga, clock_mhz = 500, precision = float32)
|
|
32
|
+
@verify(lean, theorem = "leaky_relu_monotone_in_x")
|
|
33
|
+
fn leaky_relu(x: Real, alpha: Real) -> RectifiedSignal
|
|
34
|
+
where chain_order <= 0
|
|
35
|
+
requires (alpha >= 0.0)
|
|
36
|
+
requires (alpha <= 1.0)
|
|
37
|
+
{
|
|
38
|
+
clamp(x, alpha * x, RELU_UPPER)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ReLU6 (mobile inference favourite). Clamped to [0, 6]
|
|
42
|
+
// so int8 quantisation has a sane fixed range.
|
|
43
|
+
@target(fpga, clock_mhz = 500, precision = float32)
|
|
44
|
+
@verify(lean, theorem = "relu6_bounded")
|
|
45
|
+
fn relu6(x: Real) -> RectifiedSignal
|
|
46
|
+
where chain_order <= 0
|
|
47
|
+
ensures (result >= 0.0)
|
|
48
|
+
ensures (result <= 6.0)
|
|
49
|
+
{
|
|
50
|
+
clamp(x, 0.0, 6.0)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Parametric ReLU with a fixed-but-loaded slope -- here `alpha`
|
|
54
|
+
// is the per-channel learned slope shipped as a kernel argument
|
|
55
|
+
// (the surface form a quantised PReLU layer takes).
|
|
56
|
+
@target(fpga, clock_mhz = 500, precision = float32)
|
|
57
|
+
fn prelu(x: Real, alpha: Real) -> RectifiedSignal
|
|
58
|
+
where chain_order <= 0
|
|
59
|
+
requires (alpha >= 0.0)
|
|
60
|
+
requires (alpha <= 2.0)
|
|
61
|
+
{
|
|
62
|
+
clamp(x, alpha * x, RELU_UPPER)
|
|
63
|
+
}
|