Skip to content

Commit 0621b1a

Browse files
danmcleranclaude
andcommitted
Gate float and hosted-stdlib code paths for embedded builds
Introduce TINYMIND_ENABLE_FLOAT and TINYMIND_ENABLE_HOSTED_IO opt-in macros (defaulting off, matching the existing TINYMIND_ENABLE_OSTREAMS precedent) so that targets without an FPU or full C++ stdlib can include the standalone composable layers without dragging in <cmath>, <fstream>, or <vector>. Refactor SquareRootApproximation<QValue> to integer Newton's method on the raw representation (Q-format identity sqrt(r/2^N)*2^N == isqrt(r << N)), and replace ValueConverter<double, QValue> usage in batchnorm/binary/ternary with a SFINAE-dispatched fromInteger helper so percent-based hyperparams compile to pure integer arithmetic on QValue. Add a unit_test/embedded smoke test that builds a QValue-only pipeline with none of the new macros set, acting as a regression guard against future hosted-only includes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1f6daa7 commit 0621b1a

21 files changed

Lines changed: 458 additions & 40 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ unit_test/qformat/output/
1414
unit_test/qlearn/output/
1515
unit_test/nn/output/
1616
unit_test/kan/output/
17+
unit_test/lookuptable/output/
18+
unit_test/embedded/output/

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ check :
66
cd unit_test/qformat && make clean && make && make run && cd -
77
cd unit_test/qlearn && make clean && make && make run && cd -
88
cd unit_test/lookuptable && make clean && make && make run && cd -
9+
cd unit_test/embedded && make clean && make && make run && cd -
910
cd examples/xor && make clean && make && make release && cd -
1011
cd examples/maze && make clean && make && make release && cd -
1112
cd examples/dqn_maze && make clean && make && make release && cd -

cpp/adam.hpp

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@
2222

2323
#pragma once
2424

25-
#include <cmath>
25+
#include "include/tinymind_platform.hpp"
26+
2627
#include <cstddef>
2728

29+
#if TINYMIND_ENABLE_FLOAT
30+
#include <cmath>
31+
#endif
32+
2833
namespace tinymind {
2934
/**
3035
* No-op optimizer policy (default). Weight updates use SGD with momentum.
@@ -40,23 +45,44 @@ namespace tinymind {
4045

4146
/**
4247
* Square root approximation for fixed-point QValue types.
43-
* Converts to double via getValue(), computes sqrt, converts back.
48+
*
49+
* FPU-free integer Newton's method on the raw representation.
50+
* Q-format identity: for value = r / 2^N, sqrt(value) in Q form is
51+
* isqrt(r << N) — the integer sqrt of the raw value left-shifted by
52+
* the number of fractional bits. The shift fits in the QValue's
53+
* MultiplicationResultFullWidthValueType (always 2x the raw width)
54+
* for any supported Q format. Negative inputs clamp to 0.
55+
*
56+
* Convergence: O(log W) iterations where W is the bit width of the
57+
* intermediate, so ~6 iterations for 64-bit and ~7 for 128-bit.
4458
*/
4559
template<typename ValueType>
4660
struct SquareRootApproximation
4761
{
48-
typedef typename ValueType::FullWidthValueType FullWidthValueType;
62+
typedef typename ValueType::FullWidthValueType FullWidthValueType;
63+
typedef typename ValueType::MultiplicationResultFullWidthValueType WideType;
4964

5065
static ValueType sqrt(const ValueType& value)
5166
{
52-
static const double factor = std::pow(2.0, -1.0 * static_cast<double>(ValueType::NumberOfFractionalBits));
53-
const double dval = static_cast<double>(value.getValue()) * factor;
54-
const double result = std::sqrt(dval >= 0.0 ? dval : 0.0);
55-
const FullWidthValueType rawResult = static_cast<FullWidthValueType>(result * static_cast<double>(1ULL << ValueType::NumberOfFractionalBits));
56-
return ValueType(rawResult);
67+
const FullWidthValueType raw = value.getValue();
68+
if (raw <= static_cast<FullWidthValueType>(0))
69+
{
70+
return ValueType(static_cast<FullWidthValueType>(0));
71+
}
72+
73+
const WideType n = static_cast<WideType>(raw) << ValueType::NumberOfFractionalBits;
74+
WideType x = n;
75+
WideType y = (x + static_cast<WideType>(1)) / static_cast<WideType>(2);
76+
while (y < x)
77+
{
78+
x = y;
79+
y = (x + n / x) / static_cast<WideType>(2);
80+
}
81+
return ValueType(static_cast<FullWidthValueType>(x));
5782
}
5883
};
5984

85+
#if TINYMIND_ENABLE_FLOAT
6086
template<>
6187
struct SquareRootApproximation<float>
6288
{
@@ -74,6 +100,7 @@ namespace tinymind {
74100
return std::sqrt(value);
75101
}
76102
};
103+
#endif // TINYMIND_ENABLE_FLOAT
77104

78105
/**
79106
* Adam optimizer policy for use with BackPropagationParent.
@@ -187,6 +214,7 @@ namespace tinymind {
187214
size_t mTimestep;
188215
};
189216

217+
#if TINYMIND_ENABLE_FLOAT
190218
/**
191219
* Floating-point Adam optimizer with standard hyperparameters.
192220
* Uses double-precision constants directly instead of QValue constructors.
@@ -251,5 +279,6 @@ namespace tinymind {
251279
double mBeta2;
252280
double mEpsilon;
253281
};
282+
#endif // TINYMIND_ENABLE_FLOAT
254283

255284
}

cpp/batchnorm.hpp

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
#pragma once
2424

2525
#include <cstddef>
26-
#include <cmath>
26+
#include <type_traits>
2727

2828
#include "adam.hpp"
2929
#include "nnproperties.hpp"
@@ -221,35 +221,52 @@ namespace tinymind {
221221
ValueType mNormalized[Size];
222222
bool mTraining;
223223

224-
typedef ValueConverter<double, ValueType> Converter;
224+
// Construct a ValueType representing the integer v. FPU-free for QValue:
225+
// QValue(int) treats its argument as the raw fixed-point bit pattern,
226+
// so we must use the (fixed, fractional) constructor instead.
227+
template<typename T = ValueType>
228+
static typename std::enable_if<std::is_floating_point<T>::value, T>::type
229+
fromInteger(const int v)
230+
{
231+
return static_cast<T>(v);
232+
}
233+
234+
template<typename T = ValueType>
235+
static typename std::enable_if<!std::is_floating_point<T>::value, T>::type
236+
fromInteger(const int v)
237+
{
238+
return T(static_cast<typename T::FixedPartFieldType>(v), 0u);
239+
}
225240

226241
static ValueType zero()
227242
{
228-
static const ValueType z = Converter::convertToDestinationType(0.0);
243+
static const ValueType z = fromInteger(0);
229244
return z;
230245
}
231246

232247
static ValueType one()
233248
{
234-
static const ValueType o = Converter::convertToDestinationType(1.0);
249+
static const ValueType o = fromInteger(1);
235250
return o;
236251
}
237252

238253
static ValueType sizeValue()
239254
{
240-
static const ValueType s = Converter::convertToDestinationType(static_cast<double>(Size));
255+
static const ValueType s = fromInteger(static_cast<int>(Size));
241256
return s;
242257
}
243258

244259
static ValueType momentum()
245260
{
246-
static const ValueType m = Converter::convertToDestinationType(static_cast<double>(MomentumPercent) / 100.0);
261+
static const ValueType m = fromInteger(static_cast<int>(MomentumPercent))
262+
/ fromInteger(100);
247263
return m;
248264
}
249265

250266
static ValueType epsilon()
251267
{
252-
static const ValueType e = Converter::convertToDestinationType(static_cast<double>(EpsilonPercent) / 100.0);
268+
static const ValueType e = fromInteger(static_cast<int>(EpsilonPercent))
269+
/ fromInteger(100);
253270
return e;
254271
}
255272

cpp/binarylayer.hpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include <cstddef>
2626
#include <cstdint>
27+
#include <type_traits>
2728

2829
#include "nnproperties.hpp"
2930

@@ -341,29 +342,44 @@ namespace tinymind {
341342
ValueType mBias[OutputSize];
342343
ValueType mBiasGradients[OutputSize];
343344

344-
typedef ValueConverter<double, ValueType> Converter;
345+
// Construct a ValueType representing the integer v. FPU-free for QValue:
346+
// QValue(int) treats its argument as the raw fixed-point bit pattern,
347+
// so we must use the (fixed, fractional) constructor instead.
348+
template<typename T = ValueType>
349+
static typename std::enable_if<std::is_floating_point<T>::value, T>::type
350+
fromInteger(const int v)
351+
{
352+
return static_cast<T>(v);
353+
}
354+
355+
template<typename T = ValueType>
356+
static typename std::enable_if<!std::is_floating_point<T>::value, T>::type
357+
fromInteger(const int v)
358+
{
359+
return T(static_cast<typename T::FixedPartFieldType>(v), 0u);
360+
}
345361

346362
static ValueType zero()
347363
{
348-
static const ValueType z = Converter::convertToDestinationType(0.0);
364+
static const ValueType z = fromInteger(0);
349365
return z;
350366
}
351367

352368
static ValueType one()
353369
{
354-
static const ValueType o = Converter::convertToDestinationType(1.0);
370+
static const ValueType o = fromInteger(1);
355371
return o;
356372
}
357373

358374
static ValueType negOne()
359375
{
360-
static const ValueType n = Converter::convertToDestinationType(-1.0);
376+
static const ValueType n = fromInteger(-1);
361377
return n;
362378
}
363379

364380
static ValueType intToValue(const int32_t v)
365381
{
366-
return Converter::convertToDestinationType(static_cast<double>(v));
382+
return fromInteger(static_cast<int>(v));
367383
}
368384

369385
static_assert(InputSize > 0, "Input size must be > 0.");

cpp/include/nnproperties.hpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,20 @@
9696
* Total values = I*H + H + (L-1)*(H^2 + H) + H*O + O (L hidden layers)
9797
*/
9898

99+
#include "tinymind_platform.hpp"
100+
99101
#include <cstdint>
100102
#include <cstdlib>
103+
#include <cstdio>
104+
105+
#if TINYMIND_ENABLE_FLOAT
106+
#include <cmath>
107+
#endif
108+
109+
#if TINYMIND_ENABLE_HOSTED_IO
101110
#include <fstream>
102111
#include <vector>
103-
#include <cstdio>
112+
#endif
104113

105114
namespace tinymind {
106115
template<typename SourceType>
@@ -118,6 +127,7 @@ namespace tinymind {
118127
}
119128
};
120129

130+
#if TINYMIND_ENABLE_FLOAT
121131
template<>
122132
struct ValueParser<double>
123133
{
@@ -129,7 +139,7 @@ namespace tinymind {
129139
return atof(buffer);
130140
}
131141
};
132-
142+
133143
template<>
134144
struct ValueParser<float>
135145
{
@@ -141,6 +151,7 @@ namespace tinymind {
141151
return static_cast<float>(atof(buffer));
142152
}
143153
};
154+
#endif // TINYMIND_ENABLE_FLOAT
144155

145156
template<typename SourceType, typename DestinationType>
146157
struct ValueConverter
@@ -151,6 +162,7 @@ namespace tinymind {
151162
}
152163
};
153164

165+
#if TINYMIND_ENABLE_FLOAT
154166
template<typename DestinationType>
155167
struct ValueConverter<double, DestinationType>
156168
{
@@ -170,7 +182,7 @@ namespace tinymind {
170182
{
171183
static double convertToDestinationType(const SourceType& value)
172184
{
173-
static const double factor = pow(2, -1.0 * SourceType::NumberOfFractionalBits);
185+
static const double factor = std::pow(2.0, -1.0 * SourceType::NumberOfFractionalBits);
174186
const double result = (static_cast<double>(value.getValue()) * factor);
175187

176188
return result;
@@ -205,7 +217,7 @@ namespace tinymind {
205217
{
206218
static float convertToDestinationType(const SourceType& value)
207219
{
208-
static const float factor = pow(2, -1.0f * SourceType::NumberOfFractionalBits);
220+
static const float factor = std::pow(2.0f, -1.0f * SourceType::NumberOfFractionalBits);
209221
const float result = (static_cast<float>(value.getValue()) * factor);
210222

211223
return result;
@@ -220,7 +232,9 @@ namespace tinymind {
220232
return value;
221233
}
222234
};
235+
#endif // TINYMIND_ENABLE_FLOAT
223236

237+
#if TINYMIND_ENABLE_HOSTED_IO
224238
template<typename NeuralNetworkType>
225239
struct NetworkPropertiesFileManager
226240
{
@@ -776,4 +790,5 @@ namespace tinymind {
776790
}
777791
}
778792
};
793+
#endif // TINYMIND_ENABLE_HOSTED_IO
779794
}

cpp/include/tinymind_platform.hpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Copyright (c) 2026 Dan McLeran
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all
12+
* copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*/
22+
23+
#pragma once
24+
25+
/*
26+
* Platform feature gates for TinyMind.
27+
*
28+
* All TINYMIND_ENABLE_* macros default to 0 so that a freestanding embedded
29+
* build (no FPU, no full C++ stdlib) compiles out of the box. Hosted users
30+
* opt in via -DTINYMIND_ENABLE_FLOAT=1, etc.
31+
*
32+
* TINYMIND_ENABLE_OSTREAMS - <ostream> support in qformat.hpp and other
33+
* debug-printing paths.
34+
* TINYMIND_ENABLE_FLOAT - float/double-typed code paths: Adam/RMSprop
35+
* float optimizers, Xavier initializer, and the
36+
* float/double specializations of ValueParser /
37+
* ValueConverter in nnproperties.hpp. These
38+
* paths call <cmath> functions (std::sqrt,
39+
* std::pow) that require an FPU or softfp.
40+
* TINYMIND_ENABLE_HOSTED_IO - File I/O serialization in nnproperties.hpp
41+
* (NetworkPropertiesFileManager and friends),
42+
* which pulls in <fstream> and <vector>.
43+
*/
44+
45+
#ifndef TINYMIND_ENABLE_OSTREAMS
46+
#define TINYMIND_ENABLE_OSTREAMS 0
47+
#endif
48+
49+
#ifndef TINYMIND_ENABLE_FLOAT
50+
#define TINYMIND_ENABLE_FLOAT 0
51+
#endif
52+
53+
#ifndef TINYMIND_ENABLE_HOSTED_IO
54+
#define TINYMIND_ENABLE_HOSTED_IO 0
55+
#endif

0 commit comments

Comments
 (0)