Skip to content

Commit 8240983

Browse files
committed
[ExecuTorch][Vulkan] Tests for et_vk.fused_ce
Pull Request resolved: #20942 Vulkan op-test golden for `et_vk.fused_ce` (ATen transcription of the CPU-eager loss/dlogits reference). 3 cases: all-valid, a masked label (`label<0`), and vocab >> workers (strided per-row reduce). Wires `fused_ce_test` in `targets.bzl` + `CMakeLists.txt`. ghstack-source-id: 405059109 @exported-using-ghexport Differential Revision: [D111761774](https://our.internmc.facebook.com/intern/diff/D111761774/)
1 parent 2d05a3c commit 8240983

3 files changed

Lines changed: 154 additions & 0 deletions

File tree

backends/vulkan/test/op_tests/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ if(TARGET vulkan_backend AND LIB_TORCH)
122122
vulkan_op_test(
123123
linear_dW_test ${CMAKE_CURRENT_SOURCE_DIR}/linear_dW_test.cpp test_utils
124124
)
125+
vulkan_op_test(
126+
fused_ce_test ${CMAKE_CURRENT_SOURCE_DIR}/fused_ce_test.cpp test_utils
127+
)
125128

126129
# Only build generated op tests if a path to tags.yaml and
127130
# native_functions.yaml is provided. These files are required for codegen.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <gtest/gtest.h>
10+
11+
#include <ATen/ATen.h>
12+
13+
#include <executorch/backends/vulkan/runtime/api/api.h>
14+
#include <executorch/backends/vulkan/runtime/graph/ComputeGraph.h>
15+
#include <executorch/backends/vulkan/runtime/graph/ops/OperatorRegistry.h>
16+
17+
#include "test_utils.h"
18+
19+
#include <iostream>
20+
#include <vector>
21+
22+
//
23+
// Reference implementation: transcription of the CPU-eager fused_ce_impl
24+
// (backends/vulkan/custom_ops_lib.py), computed with ATen (library golden).
25+
//
26+
27+
std::pair<at::Tensor, at::Tensor> fused_ce_reference(
28+
const at::Tensor& logits,
29+
const at::Tensor& labels,
30+
double n_valid) {
31+
at::Tensor mask = labels.ge(0);
32+
at::Tensor safe = labels.clamp_min(0).to(at::kLong);
33+
at::Tensor lse = at::logsumexp(logits, -1);
34+
at::Tensor picked = logits.gather(-1, safe.unsqueeze(-1)).squeeze(-1);
35+
at::Tensor loss =
36+
at::where(mask, (lse - picked) / n_valid, at::zeros_like(lse)).sum();
37+
at::Tensor softmax = at::softmax(logits, -1);
38+
at::Tensor onehot = at::one_hot(safe, logits.size(-1)).to(logits.dtype());
39+
at::Tensor dlogits = at::where(
40+
mask.unsqueeze(-1),
41+
(softmax - onehot) / n_valid,
42+
at::zeros_like(softmax));
43+
return {loss, dlogits};
44+
}
45+
46+
void test_vulkan_fused_ce(
47+
const int n_rows,
48+
const int vocab,
49+
const std::vector<int32_t>& labels_data,
50+
const double n_valid) {
51+
at::manual_seed(0);
52+
53+
at::Tensor logits =
54+
at::rand({n_rows, vocab}, at::device(at::kCPU).dtype(at::kFloat)) * 4.0 -
55+
2.0;
56+
at::Tensor labels =
57+
at::from_blob(
58+
const_cast<int32_t*>(labels_data.data()), {n_rows}, at::kInt)
59+
.clone();
60+
61+
auto ref = fused_ce_reference(logits, labels, n_valid);
62+
at::Tensor ref_loss = ref.first;
63+
at::Tensor ref_dlogits = ref.second;
64+
65+
using namespace vkcompute;
66+
67+
GraphConfig config;
68+
ComputeGraph graph(config);
69+
70+
IOValueRef r_logits = graph.add_input_tensor(
71+
logits.sizes().vec(), vkapi::kFloat, utils::kBuffer);
72+
IOValueRef r_labels =
73+
graph.add_input_tensor(labels.sizes().vec(), vkapi::kInt, utils::kBuffer);
74+
75+
const ValueRef r_n_valid = graph.add_scalar<double>(n_valid);
76+
77+
const ValueRef r_loss = graph.add_tensor({}, vkapi::kFloat, utils::kBuffer);
78+
const ValueRef r_dlogits =
79+
graph.add_tensor({n_rows, vocab}, vkapi::kFloat, utils::kBuffer);
80+
const ValueRef r_out = graph.add_value_list({r_loss, r_dlogits});
81+
82+
VK_GET_OP_FN("et_vk.fused_ce.default")
83+
(graph, {r_logits.value, r_labels.value, r_n_valid, r_out});
84+
85+
ValueRef staging_loss = graph.set_output_tensor(r_loss);
86+
ValueRef staging_dlogits = graph.set_output_tensor(r_dlogits);
87+
88+
graph.prepare();
89+
graph.prepack();
90+
graph.propagate_resize();
91+
92+
graph.maybe_cast_and_copy_into_staging(
93+
r_logits.staging, logits.const_data_ptr(), logits.numel(), vkapi::kFloat);
94+
graph.maybe_cast_and_copy_into_staging(
95+
r_labels.staging, labels.const_data_ptr(), labels.numel(), vkapi::kInt);
96+
97+
graph.execute();
98+
99+
at::Tensor vk_loss = at::zeros({}, at::device(at::kCPU).dtype(at::kFloat));
100+
graph.maybe_cast_and_copy_from_staging(
101+
staging_loss, vk_loss.mutable_data_ptr(), 1, vkapi::kFloat);
102+
103+
at::Tensor vk_dlogits =
104+
at::zeros({n_rows, vocab}, at::device(at::kCPU).dtype(at::kFloat));
105+
graph.maybe_cast_and_copy_from_staging(
106+
staging_dlogits,
107+
vk_dlogits.mutable_data_ptr(),
108+
vk_dlogits.numel(),
109+
vkapi::kFloat);
110+
111+
const double atol = 1e-4;
112+
const double rtol = 1e-4;
113+
114+
const bool loss_ok = at::allclose(ref_loss, vk_loss, rtol, atol);
115+
const bool dlogits_ok = at::allclose(ref_dlogits, vk_dlogits, rtol, atol);
116+
117+
if (!loss_ok || !dlogits_ok) {
118+
std::cout << "fused_ce mismatch: n_rows=" << n_rows << " vocab=" << vocab
119+
<< " n_valid=" << n_valid << std::endl;
120+
std::cout << "loss ref=" << ref_loss.item<float>()
121+
<< " vk=" << vk_loss.item<float>() << std::endl;
122+
std::cout << "max dlogits diff="
123+
<< at::max(at::abs(ref_dlogits - vk_dlogits)).item<float>()
124+
<< std::endl;
125+
}
126+
ASSERT_TRUE(loss_ok);
127+
ASSERT_TRUE(dlogits_ok);
128+
}
129+
130+
TEST(VulkanFusedCeTest, all_valid_small) {
131+
test_vulkan_fused_ce(
132+
/*n_rows=*/4, /*vocab=*/8, /*labels=*/{0, 3, 7, 1}, /*n_valid=*/4.0);
133+
}
134+
135+
TEST(VulkanFusedCeTest, masked_label) {
136+
// One masked row (label < 0): contributes 0 to loss and 0 gradient.
137+
test_vulkan_fused_ce(
138+
/*n_rows=*/4, /*vocab=*/8, /*labels=*/{2, -1, 5, 0}, /*n_valid=*/3.0);
139+
}
140+
141+
TEST(VulkanFusedCeTest, large_vocab_strided_reduce) {
142+
// vocab >> NWORKERS exercises the strided per-row reduction.
143+
test_vulkan_fused_ce(
144+
/*n_rows=*/3, /*vocab=*/200, /*labels=*/{17, -1, 199}, /*n_valid=*/2.0);
145+
}

backends/vulkan/test/op_tests/targets.bzl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,12 @@ def define_common_targets(is_fbcode = False):
192192
":test_utils",
193193
]
194194
)
195+
define_test_targets(
196+
"fused_ce_test",
197+
extra_deps = [
198+
":test_utils",
199+
]
200+
)
195201
define_test_targets(
196202
"rms_norm_test",
197203
extra_deps = [

0 commit comments

Comments
 (0)