Grid search parameter combinations in Python
split
The method does a few things at the same time.

It expands the ranges
generates the combinations
takes the sums
compares to the target
assembles a DataFrame

better would be to split it up in a few logical units. This will not only help freeing memory in-between steps, but also help with debugging the intermediate values
looping
Check the talk &quot;Looping like a pro&quot;
You loop over the index, and end up with real long variables like ranges[parameter][0]. If you use tuple expansion, you can do this:
def expand(ranges, increment):
    for start, end in ranges.values():
        expansion= list(np.arange(start, end, increment))
        # np.arange() is exclusive of the upper bound, let's fix that
        if expansion[-1] != end:
            expansion.append(end)
        yield expansion

For me, this is a lot clearer. It also has the benefit of keeping the lines shorter.
Since it are more the expansions of the ranges you create instead of the combinations, I renamed the method and variables.
This also uses a generator instead of an intermediate list.
This alternative might even simpler:
def expand(ranges, increment):
    for start, end in ranges.values():
        yield  np.arange(start, end + increment/2, increment)

This can then be combined and summed like this:
def combine_naive(expansions, target):
    for combination in itertools.product(*expansions):
        # using np.isclose() so that the algorithm works for floats
        if np.isclose(sum(combination), target):
            yield combination

I like your comment there. It explains why you use np.isclose instead of ==
Alternative approaches
numpy
instead of having itertools.product assemble the combinations, you could use numpy.meshgrid
def combine_numpy(expansions, target):
    grid = np.meshgrid(*expansions)
    summary = np.array(grid).sum(axis=0)
    indices = np.where(np.isclose(summary, target))
    return (
        [row[i] for row, i in zip(expansions, idx)]
        for idx in zip(*indices)
    )

This is approximately the same, but staying in the numpy universe. You can reduce the memory footprint here with the correct choice of dtype and reducing the number of intermediary variables
sorted
or you could use the fact the expansions are sorted, and break when a sum is over the target. This uses recursion, tuple unpacking and the fact an empty sequence is Falsey.
def combine_sorted(expansions, target, previous=()):
    series, *rest = expansions

    sum_max = sum(item[-1] for item in rest)
    for item in series:
        current = previous + (item,)
        sum_current = sum(current)
        if rest:
            if sum_current + sum_max &lt; target and not np.isclose(
                sum_current + sum_max, target
            ):
                continue
            yield from combine_sorted(rest, target, previous=current)
        else:
            if np.isclose(sum_current, target):
                yield current
            elif sum_current &gt; target:
                return

The sum_max is to skip numbers where the total some will be less than target as soon as possible.
testing and timing with real data will show which option is more memory and CPU intensive.
if __name__ == &quot;__main__&quot;:
    ranges = {&quot;a&quot;: (95, 99), &quot;b&quot;: (1, 4), &quot;c&quot;: (1, 2)}
    increment = 1.0
    target = 100.0
    df = solution(ranges, increment, target)

    expansions = list(expand(ranges, increment))
    results = list(combine_naive(expansions, target))
    df1 = pd.DataFrame(results, columns=ranges.keys())
    print(df1)

    results_sorted = list(combine_sorted(expansions, target))
    print(results_sorted)
    df_sorted = pd.DataFrame(results_sorted, columns=ranges.keys())
    print(df_sorted)

    results_numpy = list(combine_numpy(expansions, target))
    print(results_numpy)
    df_sorted = pd.DataFrame(results_numpy, columns=ranges.keys())
    print(df_sorted)