Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
763c1a9
naive fist fix
ioangatop Oct 25, 2023
d506b43
refactor solution
ioangatop Oct 25, 2023
5ddb16b
add docs
ioangatop Oct 25, 2023
4840431
add more tests
ioangatop Oct 25, 2023
0f46c82
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 25, 2023
b12ea6c
add more tests
ioangatop Oct 25, 2023
29871bd
Merge branch 'ioannis@18861-CSVLogger-fails-on-remote-fs' of https://…
ioangatop Oct 25, 2023
068a37f
fmt fix
ioangatop Oct 25, 2023
435ec6a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 25, 2023
946dc8f
fix typing
ioangatop Oct 25, 2023
6824501
Merge branch 'ioannis@18861-CSVLogger-fails-on-remote-fs' of https://…
ioangatop Oct 25, 2023
93079d8
fix typing
ioangatop Oct 25, 2023
4dedd00
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 25, 2023
e96c03f
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Oct 25, 2023
8f2fb4a
add named arg
ioangatop Oct 26, 2023
560bf81
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Oct 31, 2023
519c034
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
Borda Nov 18, 2023
b2c417f
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Nov 24, 2023
96b8b11
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Jan 11, 2024
7d9788b
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Jan 11, 2024
6810949
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Jan 15, 2024
2573803
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Feb 7, 2024
8f3efdf
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
Borda Feb 16, 2024
17c3d0b
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Mar 19, 2024
c7127c8
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Apr 22, 2024
3585e05
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
ioangatop Jun 4, 2024
f728bfa
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
Borda Apr 14, 2025
7e0ae4e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 14, 2025
9588ef8
typing
Borda Apr 14, 2025
27ae13d
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
bhimrazy Aug 28, 2025
dff5db2
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
Borda Sep 1, 2025
936dc8b
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
Borda Sep 1, 2025
ca91286
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
bhimrazy Sep 3, 2025
011f0c3
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
bhimrazy Sep 4, 2025
d85dbe4
refactor: enhance CSVLogger's metric writing logic for local and remo…
bhimrazy Sep 4, 2025
e5f7849
test: enhance CSVLogger tests for column handling and remote filesyst…
bhimrazy Sep 4, 2025
7d7d580
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
bhimrazy Sep 4, 2025
86f6a6e
revert flush_logs_every_n_steps value
bhimrazy Sep 4, 2025
509e639
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
SkafteNicki Sep 4, 2025
5be1825
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
Borda Sep 4, 2025
4e3a7ad
Merge branch 'master' into ioannis@18861-CSVLogger-fails-on-remote-fs
Borda Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions src/lightning/fabric/loggers/csv_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from argparse import Namespace
from typing import Any, Dict, List, Optional, Set, Union

from fsspec.implementations import local
from torch import Tensor

from lightning.fabric.loggers.logger import Logger, rank_zero_experiment
Expand Down Expand Up @@ -223,15 +224,14 @@ def save(self) -> None:

new_keys = self._record_new_keys()
file_exists = self._fs.isfile(self.metrics_file_path)
rewrite_file = not isinstance(self._fs, local.LocalFileSystem) or new_keys

if new_keys and file_exists:
# we need to re-write the file if the keys (header) change
self._rewrite_with_new_header(self.metrics_keys)
if rewrite_file and file_exists:
self._append_recorded_metrics()

with self._fs.open(self.metrics_file_path, mode=("a" if file_exists else "w"), newline="") as file:
with self._fs.open(self.metrics_file_path, "a" if not rewrite_file else "w", newline="") as file:
writer = csv.DictWriter(file, fieldnames=self.metrics_keys)
if not file_exists:
# only write the header if we're writing a fresh file
if rewrite_file:
writer.writeheader()
writer.writerows(self.metrics)

Expand All @@ -244,11 +244,12 @@ def _record_new_keys(self) -> Set[str]:
self.metrics_keys.extend(new_keys)
return new_keys

def _rewrite_with_new_header(self, fieldnames: List[str]) -> None:
with self._fs.open(self.metrics_file_path, "r", newline="") as file:
metrics = list(csv.DictReader(file))
def _append_recorded_metrics(self) -> None:
"""Appends the previous recorded metrics to the current ``self.metrics``."""
metrics = self._fetch_recorded_metrics()
self.metrics = metrics + self.metrics

with self._fs.open(self.metrics_file_path, "w", newline="") as file:
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(metrics)
def _fetch_recorded_metrics(self) -> List[Dict[str, Any]]:
"""Fetches the previous recorded metrics."""
with self._fs.open(self.metrics_file_path, "r", newline="") as file:
return list(csv.DictReader(file))
74 changes: 48 additions & 26 deletions tests/tests_fabric/loggers/test_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import csv
import itertools
import os
from typing import Dict, List, Set, Tuple
from unittest.mock import MagicMock

import pytest
Expand Down Expand Up @@ -150,33 +153,52 @@ def test_append_columns(tmp_path):

# initial metrics
logger.log_metrics({"a": 1, "b": 2})
_assert_csv_content(
logger.experiment.metrics_file_path,
expected_headers={"step", "a", "b"},
expected_content=[{"step": "0", "a": "1", "b": "2"}],
)

# new key appears
logger.log_metrics({"a": 1, "b": 2, "c": 3})
with open(logger.experiment.metrics_file_path) as file:
header = file.readline().strip()
assert set(header.split(",")) == {"step", "a", "b", "c"}
logger.log_metrics({"a": 11, "b": 22, "c": 33})
_assert_csv_content(
logger.experiment.metrics_file_path,
expected_headers={"step", "a", "b", "c"},
expected_content=[
{"step": "0", "a": "1", "b": "2", "c": ""},
{"step": "0", "a": "11", "b": "22", "c": "33"},
],
)

# key disappears
logger.log_metrics({"a": 1, "c": 3})
with open(logger.experiment.metrics_file_path) as file:
header = file.readline().strip()
assert set(header.split(",")) == {"step", "a", "b", "c"}


def test_rewrite_with_new_header(tmp_path):
# write a csv file manually
with open(tmp_path / "metrics.csv", "w") as file:
file.write("step,metric1,metric2\n")
file.write("0,1,22\n")

writer = _ExperimentWriter(log_dir=str(tmp_path))
new_columns = ["step", "metric1", "metric2", "metric3"]
writer._rewrite_with_new_header(new_columns)

# the rewritten file should have the new columns
with open(tmp_path / "metrics.csv") as file:
header = file.readline().strip().split(",")
assert header == new_columns
logs = file.readline().strip().split(",")
assert logs == ["0", "1", "22", ""]
logger.log_metrics({"a": 111, "c": 333})
_assert_csv_content(
logger.experiment.metrics_file_path,
expected_headers={"step", "a", "b", "c"},
expected_content=[
{"step": "0", "a": "1", "b": "2", "c": ""},
{"step": "0", "a": "11", "b": "22", "c": "33"},
{"step": "0", "a": "111", "b": "", "c": "333"},
],
)


def _assert_csv_content(
path: str,
expected_headers: Set[str],
expected_content: List[Dict[str, str]],
) -> None:
"""Verifies the content of a local csv file with the expected ones."""
headers, content = _read_csv(path)
assert headers == expected_headers
for actual, expected in itertools.zip_longest(content, expected_content):
assert actual == expected


def _read_csv(path: str) -> Tuple[Set[str], List[Dict[str, str]]]:
"""Reads a local csv file and returns the headers and content."""
with open(path) as file:
reader = csv.DictReader(file)
headers = set(reader.fieldnames)
content = list(reader)
return headers, content