Skip to content

Commit 01075e1

Browse files
danmcleranclaude
andcommitted
Fix dropout scale construction for fixed-point types
scale() previously did static_cast<ValueType>(double), which for QValue invokes the raw-bits constructor — so a 50% dropout on Q8.8 produced a scale of ~0.008 (raw=2) instead of 2.0 (raw=512), silently corrupting all fixed-point training runs that used dropout. Construct the scale from integers via a SFINAE-dispatched fromInteger helper: cast for floating-point types, (FixedPart, FractionalPart) constructor for QValue. The forward/backward logic is unchanged. Restored the fixed-point dropout training tests removed in the previous commit; they now pass and pin down the corrected behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f2da5b4 commit 01075e1

2 files changed

Lines changed: 61 additions & 9 deletions

File tree

cpp/dropout.hpp

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

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

2829
namespace tinymind {
2930
/**
@@ -157,11 +158,30 @@ namespace tinymind {
157158
}
158159
}
159160

161+
// Construct a ValueType representing the integer v. For floating-point
162+
// types this is just a cast; for QValue the (fixed, fractional)
163+
// constructor is required because QValue(int) treats its argument as
164+
// the raw fixed-point bit pattern, not the value.
165+
template<typename T = ValueType>
166+
static typename std::enable_if<std::is_floating_point<T>::value, T>::type
167+
fromInteger(const int v)
168+
{
169+
return static_cast<T>(v);
170+
}
171+
172+
template<typename T = ValueType>
173+
static typename std::enable_if<!std::is_floating_point<T>::value, T>::type
174+
fromInteger(const int v)
175+
{
176+
return T(static_cast<typename T::FixedPartFieldType>(v), 0u);
177+
}
178+
160179
static ValueType scale()
161180
{
162181
// 1 / (1 - p) where p = DropoutPercent / 100
163182
// = 100 / (100 - DropoutPercent)
164-
static const ValueType s = static_cast<ValueType>(100.0 / (100.0 - static_cast<double>(DropoutPercent)));
183+
static const ValueType s = fromInteger(100)
184+
/ fromInteger(100 - static_cast<int>(DropoutPercent));
165185
return s;
166186
}
167187

unit_test/nn/nn_unit_test.cpp

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4594,33 +4594,65 @@ BOOST_AUTO_TEST_CASE(test_case_dropout_fixed_point_inference_passthrough)
45944594
}
45954595
}
45964596

4597+
BOOST_AUTO_TEST_CASE(test_case_dropout_fixed_point_training_scaling)
4598+
{
4599+
// Q8.8 with 50% dropout: survivors must scale by exactly 2.0 = raw 512.
4600+
typedef tinymind::QValue<8, 8, true, tinymind::RoundUpPolicy> ValueType;
4601+
srand(RANDOM_SEED);
4602+
tinymind::Dropout<ValueType, 8, 50> dropout;
4603+
dropout.setTraining(true);
4604+
4605+
ValueType input[8];
4606+
for (size_t i = 0; i < 8; ++i)
4607+
{
4608+
input[i] = ValueType(1, 0); // 1.0
4609+
}
4610+
ValueType output[8];
4611+
dropout.forward(input, output);
4612+
4613+
for (size_t i = 0; i < 8; ++i)
4614+
{
4615+
if (dropout.getMask(i))
4616+
{
4617+
BOOST_TEST(output[i].getValue() == ValueType(2, 0).getValue());
4618+
}
4619+
else
4620+
{
4621+
BOOST_TEST(output[i].getValue() == 0);
4622+
}
4623+
}
4624+
}
4625+
45974626
BOOST_AUTO_TEST_CASE(test_case_dropout_high_rate_boundary)
45984627
{
45994628
// 99% dropout is the largest legal rate (the layer static_asserts < 100).
4600-
// Scale = 100, so survivors multiply by 100. Verified on double here;
4601-
// see notes in dropout.hpp for the fixed-point scale-construction issue.
4629+
// Scale = 100. Use Q16.16 since 100 doesn't fit in Q8.8's signed fixed range.
4630+
typedef tinymind::QValue<16, 16, true, tinymind::RoundUpPolicy> ValueType;
46024631
srand(RANDOM_SEED);
4603-
tinymind::Dropout<double, 16, 99> dropout;
4632+
tinymind::Dropout<ValueType, 16, 99> dropout;
46044633
dropout.setTraining(true);
46054634

4606-
double input[16];
4635+
ValueType input[16];
46074636
for (size_t i = 0; i < 16; ++i)
46084637
{
4609-
input[i] = 1.0;
4638+
input[i] = ValueType(1, 0);
46104639
}
4611-
double output[16];
4640+
ValueType output[16];
46124641
dropout.forward(input, output);
46134642

4643+
const ValueType expectedKept(100, 0);
46144644
bool sawDropped = false;
46154645
for (size_t i = 0; i < 16; ++i)
46164646
{
46174647
if (dropout.getMask(i))
46184648
{
4619-
BOOST_TEST(std::fabs(output[i] - 100.0) < 1e-9);
4649+
// 1 LSB tolerance for Q16.16 division rounding in scale construction.
4650+
const auto delta = output[i].getValue() - expectedKept.getValue();
4651+
BOOST_TEST(std::abs(delta) <= 1);
46204652
}
46214653
else
46224654
{
4623-
BOOST_TEST(output[i] == 0.0);
4655+
BOOST_TEST(output[i].getValue() == 0);
46244656
sawDropped = true;
46254657
}
46264658
}

0 commit comments

Comments
 (0)