Skip to content

Commit 0838943

Browse files
real-zhangzheAlbert
andauthored
Fix(BN mudnn api use bug) (#291)
Co-authored-by: Albert <albert@mthreads.com>
1 parent 7e03880 commit 0838943

3 files changed

Lines changed: 75 additions & 2 deletions

File tree

musa_ext/kernels/nn/musa_fused_batchnorm_kernel.mu

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ __global__ void BesselCorrectionKernel(float* data, float factor, int count) {
2020
}
2121
}
2222

23+
__global__ void VarianceToInvStdKernel(const float* variance, float* inv_std,
24+
float epsilon, int count) {
25+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
26+
if (idx < count) {
27+
inv_std[idx] = rsqrtf(variance[idx] + epsilon);
28+
}
29+
}
30+
2331
extern "C" {
2432

2533
// Launch Bessel correction kernel on the given stream.
@@ -36,4 +44,15 @@ void LaunchBesselCorrection(float* data, float factor, int count,
3644
data, factor, count);
3745
}
3846

47+
// TensorFlow exposes population variance as reserve_space_2, while muDNN's
48+
// training backward kernel consumes reciprocal standard deviation.
49+
void LaunchVarianceToInvStd(const float* variance, float* inv_std,
50+
float epsilon, int count, musaStream_t stream) {
51+
if (count <= 0) return;
52+
int block_size = 256;
53+
int grid_size = (count + block_size - 1) / block_size;
54+
VarianceToInvStdKernel<<<grid_size, block_size, 0, stream>>>(
55+
variance, inv_std, epsilon, count);
56+
}
57+
3958
} // extern "C"

musa_ext/kernels/nn/musa_fused_batchnorm_op.cc

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
extern "C" {
1717
void LaunchBesselCorrection(float* data, float factor, int count,
1818
musaStream_t stream);
19+
void LaunchVarianceToInvStd(const float* variance, float* inv_std,
20+
float epsilon, int count, musaStream_t stream);
1921
}
2022

2123
namespace tensorflow {
@@ -260,7 +262,19 @@ class MusaFusedBatchNormGradOp : public MusaOpKernel {
260262

261263
mTensor mt_scale = CreateMTensor(scale, mFormat::NCHW);
262264
mTensor mt_saved_mean = CreateMTensor(saved_mean, mFormat::NCHW);
263-
mTensor mt_saved_var = CreateMTensor(saved_var, mFormat::NCHW);
265+
// TensorFlow's reserve_space_2 contains population variance. muDNN's
266+
// training backward kernels interpret their `v` input as reciprocal
267+
// standard deviation and use it directly (including inv_std^3 terms).
268+
// Passing variance happens to look plausible when variance is near one,
269+
// but makes dx scale catastrophically for high-variance inputs.
270+
Tensor saved_inv_std;
271+
OP_REQUIRES_OK(ctx,
272+
ctx->allocate_temp(DT_FLOAT, saved_var.shape(),
273+
&saved_inv_std));
274+
LaunchVarianceToInvStd(
275+
saved_var.flat<float>().data(), saved_inv_std.flat<float>().data(),
276+
epsilon_, static_cast<int>(saved_var.NumElements()), stream);
277+
mTensor mt_saved_inv_std = CreateMTensor(saved_inv_std, mFormat::NCHW);
264278

265279
mTensor mt_d_scale = CreateMTensor(*d_scale, mFormat::NCHW);
266280
mTensor mt_d_offset = CreateMTensor(*d_offset, mFormat::NCHW);
@@ -274,7 +288,7 @@ class MusaFusedBatchNormGradOp : public MusaOpKernel {
274288

275289
mStatus status = bn_op.RunBwd(
276290
handle, mt_dx, mt_d_mean, mt_d_var, mt_d_scale, mt_d_offset, mt_x,
277-
mt_dy, mt_saved_mean, mt_saved_var, mt_scale, maintainer);
291+
mt_dy, mt_saved_mean, mt_saved_inv_std, mt_scale, maintainer);
278292

279293
OP_REQUIRES(ctx, status == mStatus::SUCCESS,
280294
errors::Internal("MUSA BN Backward failed."));

test/ops/fused_batchnorm_op_test.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,46 @@ def grad_dx_nchw(x, scale, offset, mean, var):
215215
atol=1e-3,
216216
)
217217

218+
def testLayerNormGradNCHWLargeVariance(self):
219+
"""LayerNorm's NCHW fused-BN backward uses variance correctly.
220+
221+
Keras implements rank-2 LayerNormalization with FusedBatchNormV3 by
222+
reshaping [batch, features] to NCHW [1, batch, features, 1]. TensorFlow
223+
passes population variance to FusedBatchNormGradV3, whereas muDNN's
224+
training backward API consumes reciprocal standard deviation. Passing
225+
variance directly makes dx grow by many orders of magnitude when the
226+
input variance is far from one, as seen in Wukong's FMB blocks.
227+
"""
228+
rng = np.random.default_rng(42)
229+
x_np = (10.0 * rng.normal(size=(128, 1536))).astype(np.float32)
230+
dy_np = rng.normal(scale=1e-3, size=x_np.shape).astype(np.float32)
231+
232+
def run(device, dtype):
233+
layer_dtype = (
234+
"mixed_bfloat16" if dtype == tf.bfloat16 else "float32"
235+
)
236+
with tf.device(device):
237+
x = tf.cast(tf.constant(x_np), dtype)
238+
dy = tf.cast(tf.constant(dy_np), dtype)
239+
layer = tf.keras.layers.LayerNormalization(
240+
axis=-1, epsilon=1e-3, dtype=layer_dtype
241+
)
242+
with tf.GradientTape() as tape:
243+
tape.watch(x)
244+
y = layer(x)
245+
dx = tape.gradient(y, x, output_gradients=tf.cast(dy, y.dtype))
246+
return tf.cast(dx, tf.float32)
247+
248+
for dtype in (tf.float32, tf.bfloat16):
249+
dx_cpu = run("/CPU:0", dtype)
250+
dx_musa = run("/device:MUSA:0", dtype)
251+
self.assertAllClose(
252+
dx_musa.numpy(),
253+
dx_cpu.numpy(),
254+
rtol=0.15,
255+
atol=5e-5,
256+
)
257+
218258
def testFusedBatchNormGradDXNotAffectedByPriorMemory(self):
219259
"""BN backward dx must not inherit values from a previously freed buffer.
220260

0 commit comments

Comments
 (0)