diff --git a/predtuner/approxapp.py b/predtuner/approxapp.py
index a6513cf19dea61fa17d6e541d8fa56b568b1c336..b420d8deaf35ad3ac25f8d3a6ef507997c9d3116 100644
--- a/predtuner/approxapp.py
+++ b/predtuner/approxapp.py
@@ -113,9 +113,13 @@ class Config:
         self.knobs = dict(sorted(knobs.items()))
         self.test_qos: Optional[float] = test_qos
 
+    @property
+    def speedup(self):
+        return 1 / self.cost
+        
     @property
     def qos_speedup(self):
-        return self.qos, 1 / self.cost
+        return self.qos, self.speedup
 
 
 T = TypeVar("T", bound=Config)
diff --git a/predtuner/pipedbin.py b/predtuner/pipedbin.py
index 9b9723a75d1d35fb77e407a22e9b540592642500..b5d525ddf8e81f53271afde486f43a597b26a1a5 100644
--- a/predtuner/pipedbin.py
+++ b/predtuner/pipedbin.py
@@ -1,9 +1,9 @@
 import json
 import os
 from pathlib import Path
-from typing import Dict, List, Optional, Tuple, Union
-import numpy as np
+from typing import Dict, List, Optional, Sequence, Tuple, Union
 
+import numpy as np
 import torch
 
 from .approxapp import ApproxKnob, BaselineKnob, KnobsT
@@ -14,6 +14,7 @@ from .modeledapp import (
     ModeledApp,
     QoSModelP1,
     QoSModelP2,
+    ValConfig,
 )
 from .torchutil import accuracy
 
@@ -58,6 +59,30 @@ class PipedBinaryApp(ModeledApp):
         self.process = None
         self._invoke_binary()
 
+        # Measure our own test set accuracy for config export purpose
+        self.base_test_qos, _ = self.empirical_measure_qos_cost({}, True)
+
+    def dump_hpvm_configs(
+        self,
+        configs: Sequence[ValConfig],
+        output: PathLike = None,
+        add_baseline_conf: bool = True,
+    ):
+        configs = list(configs)
+        tqos = self.base_test_qos
+        if add_baseline_conf:
+            base_knobs = self.add_baseline_to_knobs({})
+            base_conf = ValConfig(tqos, 1.0, base_knobs, tqos, tqos)
+            configs = [base_conf] + configs
+        configs = sorted(configs, key=lambda c: c.speedup)
+        conf_str = self.knob_exporter.configs_to_str(configs, tqos)
+        if output is None:
+            return conf_str
+        output_p = Path(output)
+        os.makedirs(output_p.parent, exist_ok=True)
+        with output_p.open("w") as f:
+            f.write(conf_str)
+
     @property
     def name(self) -> str:
         """Name of application. Acts as an identifier in many places, so
@@ -107,7 +132,7 @@ class PipedBinaryApp(ModeledApp):
         self._check_running()
         conf = self.add_baseline_to_knobs(with_approxes)
         with self.conf_file.open("w") as f:
-            f.write(self.knob_exporter.to_str(conf))
+            f.write(self.knob_exporter.knobs_to_str(conf))
         with self.fifo_w_file.open("w") as f:
             f.write("test" if is_test else "tune")
         ret = read_till_end(self.fifo_r_file)
@@ -243,26 +268,41 @@ class HPVMConfigBuilder:
         self.types = self._parse_ops(ops)
         self.merged_to_original = self._find_merge_chains(self.types)
 
-    def to_str(self, config: KnobsT) -> str:
-        def print_op(op_index: int):
-            ty = self.types[op_index]
-            knob_value = config[self.ops[op_index]]
-            out_knob_ty = self.knob_to_knob[knob_value]
-            out_op_ty = self.op_to_op.get(ty, ty)
-            return f"{out_op_ty} {out_knob_ty} {knob_value}"
+    def knobs_to_str(self, knobs: KnobsT) -> str:
+        dummy_conf = ValConfig(1.0, 1.0, knobs, 1.0, 1.0)
+        return self.configs_to_str([dummy_conf], 1.0)
+
+    def configs_to_str(self, configs: Sequence[ValConfig], base_test_qos: float) -> str:
+        body = "\n".join(
+            self.config_to_str(config, base_test_qos, i)
+            for i, config in enumerate(configs)
+        )
+        return f"0.0\n{body}"
 
+    def config_to_str(
+        self, config: ValConfig, base_test_qos: float, conf_idx: int = 1
+    ) -> str:
         def print_line(line_index: int, op_indices):
-            return f"{line_index} gpu " + " ".join(print_op(idx) for idx in op_indices)
+            ops = " ".join(self._print_op(config.knobs, idx) for idx in op_indices)
+            return f"{line_index} gpu {ops}"
 
-        if len(config) != len(self.ops):
+        if len(config.knobs) != len(self.ops):
             raise ValueError(f"Incorrect config length, expected {len(self.ops)}")
-        prefix = ["0.0", "+++++", "conf1 0.0 0.0 0.0 0.0"]
-        suffix = ["-----"]
+        test_qos = config.test_qos
+        qos_loss = base_test_qos - config.test_qos
+        conf_header = f"conf{conf_idx} {config.speedup} 1.0 {test_qos} {qos_loss}"
         body_lines = [
             print_line(line_idx, orig_indices)
             for line_idx, orig_indices in enumerate(self.merged_to_original, start=1)
         ]
-        return "\n".join(prefix + body_lines + suffix)
+        return "\n".join(["+++++", conf_header] + body_lines + ["-----"])
+
+    def _print_op(self, knobs: KnobsT, op_index: int):
+        ty = self.types[op_index]
+        knob_value = knobs[self.ops[op_index]]
+        out_knob_ty = self.knob_to_knob[knob_value]
+        out_op_ty = self.op_to_op.get(ty, ty)
+        return f"{out_op_ty} {out_knob_ty} {knob_value}"
 
     @staticmethod
     def _parse_ops(ops: List[str]) -> List[str]: