Skip to content

Commit f014e51

Browse files
kevinjzhangmeta-codesync[bot]
authored andcommitted
Save ACE state inside compressor when flag is passed (#733)
Summary: Pull Request resolved: #733 Adds a flag `--save-ace-state` to the `zli` that saves the ace state to the serialized compressor produced. Implements this by preserving the ACE state local parameter after graph replacement is done. Reviewed By: daniellerozenblit Differential Revision: D102877314 fbshipit-source-id: f0455f2e21aeba3b034bc4fd5eba79a72702275b
1 parent 935868a commit f014e51

6 files changed

Lines changed: 166 additions & 7 deletions

File tree

cli/args/TrainArgs.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ class TrainArgs : public GlobalArgs, public ProfileArgs {
108108
0,
109109
false,
110110
"Enables pareto frontier training. This will output a directory containing all compressors in the pareto frontier.");
111+
parser.addCommandFlag(
112+
cmd(),
113+
kSaveAceState,
114+
0,
115+
false,
116+
"Save the ACE state as a local parameter in the trained compressor.");
111117
}
112118

113119
explicit TrainArgs(const arg::ParsedArgs& parsed)
@@ -184,6 +190,7 @@ class TrainArgs : public GlobalArgs, public ProfileArgs {
184190
parsed.cmdHasFlag(cmd(), kNoAceSuccessors);
185191

186192
trainParams.noClustering = parsed.cmdHasFlag(cmd(), kNoClustering);
193+
trainParams.saveAceState = parsed.cmdHasFlag(cmd(), kSaveAceState);
187194
trainParams.compressorGenFunc =
188195
custom_parsers::createCompressorFromSerialized;
189196
}
@@ -226,6 +233,7 @@ class TrainArgs : public GlobalArgs, public ProfileArgs {
226233
inline static const std::string kMaxFileSizeMb = "max-file-size-mb";
227234
inline static const std::string kMaxTotalSizeMb = "max-total-size-mb";
228235
inline static const std::string kParetoFrontier = "pareto-frontier";
236+
inline static const std::string kSaveAceState = "save-ace-state";
229237
};
230238

231239
} // namespace openzl::cli

cli/tests/BUCK

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,3 +440,17 @@ custom_unittest(
440440
":integration_test_bin",
441441
],
442442
)
443+
444+
custom_unittest(
445+
name = "csv_save_ace_state_test",
446+
command = [
447+
"$(location :integration_test_bin)",
448+
"$(location ..:zli)",
449+
"CsvSaveAceStateTest.test_train_compress_decompress",
450+
],
451+
type = "simple",
452+
deps = [
453+
"..:zli",
454+
":integration_test_bin",
455+
],
456+
)

cli/tests/cli_integration_tests.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,24 @@ def test_train_compress_decompress(self):
118118
self.train_compress_decompress()
119119

120120

121+
class CsvSaveAceStateTest(_CsvBaseTest):
122+
"""
123+
Test case for CSV training with --save-ace-state flag.
124+
125+
Verifies that training with --save-ace-state produces a compressor
126+
that correctly compresses and decompresses files.
127+
"""
128+
129+
def test_train_compress_decompress(self):
130+
execute_train(
131+
compressor_info=self.training_compressor_info,
132+
uncompressed_dir=input_dir_path(self.input_dir_name),
133+
trained_compressor_path=self.compressor_info.compressor_str,
134+
extra_args="--save-ace-state",
135+
)
136+
self.compress_and_decompress_samples()
137+
138+
121139
class CsvFullSplitTest(_CsvBaseTest):
122140
"""
123141
Test case for CSV training and compression using the full-split trainer.

tools/training/ace/ace_combination.cpp

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ namespace {
2828
*/
2929
std::shared_ptr<const std::string_view> runReplacements(
3030
Compressor& compressor,
31-
const std::unordered_map<std::string, ACECompressor>& replacements)
31+
const std::unordered_map<std::string, ACECompressor>& replacements,
32+
bool saveAceState)
3233
{
3334
// Add each graph to the compressor
3435
std::unordered_map<std::string, ZL_GraphID> newGraphIds;
@@ -40,8 +41,17 @@ std::shared_ptr<const std::string_view> runReplacements(
4041
// Replace each backend graph with the new GraphID
4142
for (const auto& [backendGraph, newGraphId] : newGraphIds) {
4243
auto backendGraphId = compressor.getGraph(backendGraph);
44+
auto localParams = LocalParams(ZL_Compressor_Graph_getLocalParams(
45+
compressor.get(), backendGraphId.value()));
4346
compressor.unwrap(ZL_Compressor_overrideBaseGraph(
4447
compressor.get(), backendGraphId.value(), newGraphId));
48+
if (saveAceState) {
49+
auto gp = ZL_GraphParameters{ .localParams = localParams.get() };
50+
compressor.unwrap(
51+
ZL_Compressor_overrideGraphParams(
52+
compressor.get(), backendGraphId.value(), &gp),
53+
"Graph replacement failed");
54+
}
4555
}
4656

4757
auto serialized = compressor.serialize();
@@ -85,15 +95,16 @@ std::shared_ptr<const std::string_view> getSmallestCandidate(
8595
const std::unordered_map<
8696
std::string,
8797
std::vector<std::pair<ACECompressor, ACECompressionResult>>>&
88-
allCandidates)
98+
allCandidates,
99+
bool saveAceState)
89100
{
90101
auto compressor = makeCompressor();
91102
std::unordered_map<std::string, ACECompressor> replacements;
92103
replacements.reserve(allCandidates.size());
93104
for (const auto& [backendGraph, candidates] : allCandidates) {
94105
replacements.emplace(backendGraph, candidates[0].first);
95106
}
96-
return runReplacements(compressor, replacements);
107+
return runReplacements(compressor, replacements, saveAceState);
97108
}
98109

99110
/**
@@ -127,7 +138,8 @@ std::shared_ptr<const std::string_view> makeCombinedCompressor(
127138
const std::unordered_map<
128139
std::string,
129140
std::vector<std::pair<ACECompressor, ACECompressionResult>>>&
130-
allCandidates)
141+
allCandidates,
142+
bool saveAceState)
131143
{
132144
const auto& choices = candidate.choices();
133145
if (allCandidates.size() != choices.size()) {
@@ -143,7 +155,7 @@ std::shared_ptr<const std::string_view> makeCombinedCompressor(
143155
replacements.emplace(name, compressor);
144156
}
145157
auto compressor = makeCompressor();
146-
return runReplacements(compressor, replacements);
158+
return runReplacements(compressor, replacements, saveAceState);
147159
}
148160

149161
std::vector<std::pair<ACECompressor, ACECompressionResult>> benchmarkAce(
@@ -276,6 +288,7 @@ std::vector<std::shared_ptr<const std::string_view>> getCombinedCompressors(
276288
return std::move(
277289
*trainParams.compressorGenFunc(*trainedSerializedCompressor));
278290
};
291+
279292
auto compressor = makeCompressor();
280293
auto cctx = refCCtxForTraining(compressor);
281294
auto serialized = compressor.serialize();
@@ -333,7 +346,8 @@ std::vector<std::shared_ptr<const std::string_view>> getCombinedCompressors(
333346
}
334347

335348
if (!trainParams.paretoFrontier) {
336-
return { getSmallestCandidate(makeCompressor, allCandidates) };
349+
return { getSmallestCandidate(
350+
makeCompressor, allCandidates, trainParams.saveAceState) };
337351
}
338352
std::vector<std::vector<CandidateSelection>> candidates;
339353
candidates.reserve(allCandidates.size());
@@ -347,7 +361,10 @@ std::vector<std::shared_ptr<const std::string_view>> getCombinedCompressors(
347361
paretoOptimalResults.reserve(frontier.size());
348362
for (auto& candidate : frontier) {
349363
paretoOptimalResults.push_back(makeCombinedCompressor(
350-
candidate, makeCompressor, allCandidates));
364+
candidate,
365+
makeCompressor,
366+
allCandidates,
367+
trainParams.saveAceState));
351368
}
352369
return paretoOptimalResults;
353370
}

tools/training/tests/test_ace_combination.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
#include <gtest/gtest.h>
44
#include <random>
5+
#include "custom_parsers/dependency_registration.h"
6+
#include "openzl/cpp/CCtx.hpp"
7+
#include "openzl/cpp/codecs/ACE.hpp"
8+
#include "tools/training/ace/ace.h"
59
#include "tools/training/ace/ace_combination.h"
610

711
namespace openzl {
@@ -142,6 +146,103 @@ TEST_F(ACECombinationTest, ProducesParetoOptimalCombination)
142146
EXPECT_TRUE(isPareto(frontier));
143147
}
144148

149+
TEST_F(ACECombinationTest, NoSaveAceStateProducesSmallerCompressor)
150+
{
151+
// Create sample data: triple delta pattern compresses well with ACE
152+
std::vector<uint64_t> data(1000, 1);
153+
for (size_t i = 1; i < data.size(); ++i) {
154+
data[i] += data[i - 1];
155+
}
156+
for (size_t i = 1; i < data.size(); ++i) {
157+
data[i] += data[i - 1];
158+
}
159+
for (size_t i = 1; i < data.size(); ++i) {
160+
data[i] += data[i - 1];
161+
}
162+
auto input = Input::refSerial(data.data(), data.size() * sizeof(data[0]));
163+
std::vector<Input> inputsVec;
164+
inputsVec.push_back(std::move(input));
165+
std::vector<training::MultiInput> multiInputs;
166+
multiInputs.emplace_back(std::move(inputsVec));
167+
168+
auto compressorGenFunc = [](poly::string_view serialized) {
169+
auto compressor = std::make_unique<Compressor>();
170+
compressor->deserialize(serialized);
171+
return compressor;
172+
};
173+
174+
// Train with saveAceState = true
175+
std::shared_ptr<const std::string_view> resultWithAceState;
176+
{
177+
Compressor compressor;
178+
compressor.selectStartingGraph(graphs::ACE()(compressor));
179+
compressor.setParameter(CParam::FormatVersion, ZL_MAX_FORMAT_VERSION);
180+
training::TrainParams trainParams = {
181+
.compressorGenFunc = compressorGenFunc,
182+
.threads = 1,
183+
.saveAceState = true,
184+
};
185+
ACETrainer trainer;
186+
auto results =
187+
trainer.train(multiInputs, compressor.serialize(), trainParams);
188+
ASSERT_FALSE(results.empty());
189+
resultWithAceState = results[0];
190+
}
191+
192+
// Train with saveAceState = false (default)
193+
std::shared_ptr<const std::string_view> resultWithoutAceState;
194+
{
195+
Compressor compressor;
196+
compressor.selectStartingGraph(graphs::ACE()(compressor));
197+
compressor.setParameter(CParam::FormatVersion, ZL_MAX_FORMAT_VERSION);
198+
training::TrainParams trainParams = {
199+
.compressorGenFunc = compressorGenFunc,
200+
.threads = 1,
201+
.saveAceState = false,
202+
};
203+
ACETrainer trainer;
204+
auto results =
205+
trainer.train(multiInputs, compressor.serialize(), trainParams);
206+
ASSERT_FALSE(results.empty());
207+
resultWithoutAceState = results[0];
208+
}
209+
210+
auto sizeWithAceState = resultWithAceState->size();
211+
auto sizeWithoutAceState = resultWithoutAceState->size();
212+
213+
// Serialized compressor without ACE state should be significantly smaller
214+
EXPECT_GT(sizeWithAceState, 0);
215+
EXPECT_GT(sizeWithoutAceState, 0);
216+
EXPECT_LE(sizeWithoutAceState, sizeWithAceState / 2)
217+
<< "Serialized compressor without ACE state ("
218+
<< sizeWithoutAceState
219+
<< " bytes) should be at most half the size of one with ACE state ("
220+
<< sizeWithAceState << " bytes)";
221+
222+
// Compress data with both trained compressors and verify identical output
223+
auto compressWithResult =
224+
[&](const std::string_view& serializedCompressor) {
225+
auto comp = compressorGenFunc(serializedCompressor);
226+
CCtx cctx;
227+
cctx.setParameter(CParam::FormatVersion, ZL_MAX_FORMAT_VERSION);
228+
cctx.refCompressor(*comp);
229+
auto inputForCompress = Input::refSerial(
230+
data.data(), data.size() * sizeof(data[0]));
231+
return cctx.compressOne(inputForCompress);
232+
};
233+
auto compressedWith = compressWithResult(*resultWithAceState);
234+
auto compressedWithout = compressWithResult(*resultWithoutAceState);
235+
236+
// Compressed output should be nearly identical — the small difference
237+
// is due to ACE training being non-deterministic across independent runs.
238+
// Allow up to 10% tolerance.
239+
auto maxSize = std::max(compressedWith.size(), compressedWithout.size());
240+
auto minSize = std::min(compressedWith.size(), compressedWithout.size());
241+
EXPECT_LE(maxSize - minSize, maxSize / 10)
242+
<< "Compressed data sizes should be within 10%: "
243+
<< compressedWith.size() << " vs " << compressedWithout.size();
244+
}
245+
145246
} // namespace tests
146247
} // namespace training
147248
} // namespace openzl

tools/training/train_params.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct TrainParams {
2929
poly::optional<size_t> maxFileSizeMb;
3030
poly::optional<size_t> maxTotalSizeMb;
3131
bool paretoFrontier{ false };
32+
bool saveAceState{ false };
3233
};
3334

3435
} // namespace openzl::training

0 commit comments

Comments
 (0)