edit: Actually, this is probably wrong. I think you’re running into a limitation of SetPrimaryPartCFrame. I agree with @polarisqte, either use welds or do the relative offsets yourself.
You’re experiencing floating point precision errors, probably. Don’t set the next frame’s CFrame based on the last frame’s. Try something like this:
local angle = 0
local billboard = workspace.Billboard
local base = billboard.PrimaryPart.CFrame
local rotPerSecond = 0.02
game:GetService("RunService").RenderStepped:Connect(function(dt)
angle = angle + 2 * math.pi * rotPerSecond * dt
billboard:SetPrimaryPartCFrame(base * CFrame.Angles(0, angle, 0));
end)